mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-20 10:45:52 +00:00
pref: member list (#4344)
* chore: search member new api * chore: permission * fix: ts error * fix: member modal
This commit is contained in:
@@ -1,9 +1,8 @@
|
|||||||
// orgId, pathid, path === null ===> root org
|
|
||||||
export type postCreateOrgData = {
|
export type postCreateOrgData = {
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
path?: string;
|
orgId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type putUpdateOrgMembersData = {
|
export type putUpdateOrgMembersData = {
|
||||||
|
26
packages/global/support/user/team/type.d.ts
vendored
26
packages/global/support/user/team/type.d.ts
vendored
@@ -70,7 +70,13 @@ export type TeamTmbItemType = {
|
|||||||
permission: TeamPermission;
|
permission: TeamPermission;
|
||||||
} & ThirdPartyAccountType;
|
} & ThirdPartyAccountType;
|
||||||
|
|
||||||
export type TeamMemberItemType = {
|
export type TeamMemberItemType<
|
||||||
|
Options extends {
|
||||||
|
withPermission?: boolean;
|
||||||
|
withOrgs?: boolean;
|
||||||
|
withGroupRole?: boolean;
|
||||||
|
} = { withPermission: true; withOrgs: true; withGroupRole: false }
|
||||||
|
> = {
|
||||||
userId: string;
|
userId: string;
|
||||||
tmbId: string;
|
tmbId: string;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
@@ -78,12 +84,24 @@ export type TeamMemberItemType = {
|
|||||||
avatar: string;
|
avatar: string;
|
||||||
role: `${TeamMemberRoleEnum}`;
|
role: `${TeamMemberRoleEnum}`;
|
||||||
status: `${TeamMemberStatusEnum}`;
|
status: `${TeamMemberStatusEnum}`;
|
||||||
permission: TeamPermission;
|
|
||||||
contact?: string;
|
contact?: string;
|
||||||
createTime: Date;
|
createTime: Date;
|
||||||
updateTime?: Date;
|
updateTime?: Date;
|
||||||
orgs?: string[]; // full path name, pattern: /teamName/orgname1/orgname2
|
} & (Options extends { withPermission: true }
|
||||||
};
|
? {
|
||||||
|
permission: TeamPermission;
|
||||||
|
}
|
||||||
|
: {}) &
|
||||||
|
(Options extends { withOrgs: true }
|
||||||
|
? {
|
||||||
|
orgs?: string[]; // full path name, pattern: /teamName/orgname1/orgname2
|
||||||
|
}
|
||||||
|
: {}) &
|
||||||
|
(Options extends { withGroupRole: true }
|
||||||
|
? {
|
||||||
|
groupRole?: `${GroupMemberRole}`;
|
||||||
|
}
|
||||||
|
: {});
|
||||||
|
|
||||||
export type TeamTagItemType = {
|
export type TeamTagItemType = {
|
||||||
label: string;
|
label: string;
|
||||||
|
@@ -19,7 +19,7 @@ import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
|||||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import PermissionSelect from './PermissionSelect';
|
import PermissionSelect from './PermissionSelect';
|
||||||
import PermissionTags from './PermissionTags';
|
import PermissionTags from './PermissionTags';
|
||||||
import {
|
import {
|
||||||
@@ -39,6 +39,7 @@ import { GetSearchUserGroupOrg } from '@/web/support/user/api';
|
|||||||
import useOrg from '@/web/support/user/team/org/hooks/useOrg';
|
import useOrg from '@/web/support/user/team/org/hooks/useOrg';
|
||||||
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
|
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
|
||||||
import { MemberGroupListItemType } from '@fastgpt/global/support/permission/memberGroup/type';
|
import { MemberGroupListItemType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
const HoverBoxStyle = {
|
const HoverBoxStyle = {
|
||||||
bgColor: 'myGray.50',
|
bgColor: 'myGray.50',
|
||||||
@@ -55,7 +56,6 @@ function MemberModal({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
const collaboratorList = useContextSelector(CollaboratorContext, (v) => v.collaboratorList);
|
const collaboratorList = useContextSelector(CollaboratorContext, (v) => v.collaboratorList);
|
||||||
const [searchText, setSearchText] = useState<string>('');
|
|
||||||
const [filterClass, setFilterClass] = useState<'member' | 'org' | 'group'>();
|
const [filterClass, setFilterClass] = useState<'member' | 'org' | 'group'>();
|
||||||
const {
|
const {
|
||||||
paths,
|
paths,
|
||||||
@@ -63,18 +63,35 @@ function MemberModal({
|
|||||||
members: orgMembers,
|
members: orgMembers,
|
||||||
MemberScrollData: OrgMemberScrollData,
|
MemberScrollData: OrgMemberScrollData,
|
||||||
onPathClick,
|
onPathClick,
|
||||||
orgs
|
orgs,
|
||||||
} = useOrg({ getPermission: false });
|
searchKey,
|
||||||
|
setSearchKey
|
||||||
|
} = useOrg({ withPermission: false });
|
||||||
|
|
||||||
const { data: members, ScrollData: TeamMemberScrollData } = useScrollPagination(getTeamMembers, {
|
const {
|
||||||
pageSize: 15
|
data: members,
|
||||||
|
ScrollData: TeamMemberScrollData,
|
||||||
|
refreshList
|
||||||
|
} = useScrollPagination(getTeamMembers, {
|
||||||
|
pageSize: 15,
|
||||||
|
params: {
|
||||||
|
withPermission: true,
|
||||||
|
withOrgs: true,
|
||||||
|
status: 'active',
|
||||||
|
searchKey
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: groups = [], loading: loadingGroupsAndOrgs } = useRequest2(
|
const {
|
||||||
|
data: groups = [],
|
||||||
|
loading: loadingGroupsAndOrgs,
|
||||||
|
runAsync: refreshGroups
|
||||||
|
} = useRequest2(
|
||||||
async () => {
|
async () => {
|
||||||
if (!userInfo?.team?.teamId) return [];
|
if (!userInfo?.team?.teamId) return [];
|
||||||
return getGroupList<false>({
|
return getGroupList<false>({
|
||||||
withMembers: false
|
withMembers: false,
|
||||||
|
searchKey
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -83,53 +100,20 @@ function MemberModal({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: searchedData } = useRequest2(() => GetSearchUserGroupOrg(searchText), {
|
const search = _.debounce(() => {
|
||||||
manual: false,
|
refreshList();
|
||||||
throttleWait: 500,
|
refreshGroups();
|
||||||
debounceWait: 200,
|
}, 200);
|
||||||
refreshDeps: [searchText]
|
|
||||||
});
|
useEffect(search, [searchKey]);
|
||||||
|
|
||||||
const [selectedOrgList, setSelectedOrgIdList] = useState<OrgListItemType[]>([]);
|
const [selectedOrgList, setSelectedOrgIdList] = useState<OrgListItemType[]>([]);
|
||||||
|
|
||||||
const filterOrgs: (OrgListItemType & { count?: number })[] = useMemo(() => {
|
|
||||||
if (searchText && searchedData) {
|
|
||||||
const orgids = searchedData.orgs.map((item) => item._id);
|
|
||||||
return orgs.filter((org) => orgids.includes(String(org._id)));
|
|
||||||
}
|
|
||||||
return orgs
|
|
||||||
.filter((org) => org.path !== '')
|
|
||||||
.map((org) => ({
|
|
||||||
...org,
|
|
||||||
count: org.total
|
|
||||||
}));
|
|
||||||
}, [searchText, orgs, searchedData]);
|
|
||||||
|
|
||||||
const [selectedMemberList, setSelectedMemberList] = useState<
|
const [selectedMemberList, setSelectedMemberList] = useState<
|
||||||
Omit<TeamMemberItemType, 'permission' | 'teamId'>[]
|
Omit<TeamMemberItemType, 'permission' | 'teamId'>[]
|
||||||
>([]);
|
>([]);
|
||||||
const filterMembers = useMemo(() => {
|
|
||||||
if (searchText) {
|
|
||||||
return searchedData?.members || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return members;
|
|
||||||
}, [searchText, members, searchedData?.members]);
|
|
||||||
|
|
||||||
const [selectedGroupList, setSelectedGroupList] = useState<MemberGroupListItemType<false>[]>([]);
|
const [selectedGroupList, setSelectedGroupList] = useState<MemberGroupListItemType<false>[]>([]);
|
||||||
const filterGroups = useMemo(() => {
|
|
||||||
if (searchText) {
|
|
||||||
return searchedData?.groups.map((item) => ({
|
|
||||||
groupName: item.name,
|
|
||||||
_id: item.id,
|
|
||||||
...item
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (!searchText && filterClass !== 'group') return [];
|
|
||||||
|
|
||||||
return groups;
|
|
||||||
}, [searchText, filterClass, groups, searchedData]);
|
|
||||||
|
|
||||||
const permissionList = useContextSelector(CollaboratorContext, (v) => v.permissionList);
|
const permissionList = useContextSelector(CollaboratorContext, (v) => v.permissionList);
|
||||||
const getPerLabelList = useContextSelector(CollaboratorContext, (v) => v.getPerLabelList);
|
const getPerLabelList = useContextSelector(CollaboratorContext, (v) => v.getPerLabelList);
|
||||||
const [selectedPermission, setSelectedPermission] = useState<number | undefined>(
|
const [selectedPermission, setSelectedPermission] = useState<number | undefined>(
|
||||||
@@ -225,12 +209,12 @@ function MemberModal({
|
|||||||
<SearchInput
|
<SearchInput
|
||||||
placeholder={t('user:search_group_org_user')}
|
placeholder={t('user:search_group_org_user')}
|
||||||
bgColor="myGray.50"
|
bgColor="myGray.50"
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
onChange={(e) => setSearchKey(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Flex flexDirection="column" mt="3" overflow={'auto'} flex={'1 0 0'} h={0}>
|
<Flex flexDirection="column" mt="3" overflow={'auto'} flex={'1 0 0'} h={0}>
|
||||||
{/* Entry */}
|
{/* Entry */}
|
||||||
{!searchText && !filterClass && (
|
{!searchKey && !filterClass && (
|
||||||
<>
|
<>
|
||||||
{entryList.current.map((item) => {
|
{entryList.current.map((item) => {
|
||||||
return (
|
return (
|
||||||
@@ -257,7 +241,7 @@ function MemberModal({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Path */}
|
{/* Path */}
|
||||||
{!searchText && filterClass && (
|
{!searchKey && filterClass && (
|
||||||
<Box mb={1}>
|
<Box mb={1}>
|
||||||
<Path
|
<Path
|
||||||
paths={[
|
paths={[
|
||||||
@@ -291,9 +275,9 @@ function MemberModal({
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{(filterClass === 'member' || (searchText && filterMembers.length > 0)) &&
|
{(filterClass === 'member' || searchKey) &&
|
||||||
(() => {
|
(() => {
|
||||||
const members = filterMembers?.map((member) => {
|
const Members = members?.map((member) => {
|
||||||
const onChange = () => {
|
const onChange = () => {
|
||||||
setSelectedMemberList((state) => {
|
setSelectedMemberList((state) => {
|
||||||
if (state.find((v) => v.tmbId === member.tmbId)) {
|
if (state.find((v) => v.tmbId === member.tmbId)) {
|
||||||
@@ -315,8 +299,8 @@ function MemberModal({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return searchText ? (
|
return searchKey ? (
|
||||||
members
|
Members
|
||||||
) : (
|
) : (
|
||||||
<TeamMemberScrollData
|
<TeamMemberScrollData
|
||||||
flexDirection={'column'}
|
flexDirection={'column'}
|
||||||
@@ -324,13 +308,13 @@ function MemberModal({
|
|||||||
userSelect={'none'}
|
userSelect={'none'}
|
||||||
height={'fit-content'}
|
height={'fit-content'}
|
||||||
>
|
>
|
||||||
{members}
|
{Members}
|
||||||
</TeamMemberScrollData>
|
</TeamMemberScrollData>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
{(filterClass === 'org' || searchText) &&
|
{(filterClass === 'org' || searchKey) &&
|
||||||
(() => {
|
(() => {
|
||||||
const orgs = filterOrgs?.map((org) => {
|
const Orgs = orgs?.map((org) => {
|
||||||
const onChange = () => {
|
const onChange = () => {
|
||||||
setSelectedOrgIdList((state) => {
|
setSelectedOrgIdList((state) => {
|
||||||
if (state.find((v) => v._id === org._id)) {
|
if (state.find((v) => v._id === org._id)) {
|
||||||
@@ -356,18 +340,18 @@ function MemberModal({
|
|||||||
pointerEvents="none"
|
pointerEvents="none"
|
||||||
/>
|
/>
|
||||||
<MyAvatar src={org.avatar} w="1.5rem" borderRadius={'50%'} />
|
<MyAvatar src={org.avatar} w="1.5rem" borderRadius={'50%'} />
|
||||||
<HStack ml="2" w="full" gap="5px">
|
<HStack w="full">
|
||||||
<Text>{org.name}</Text>
|
<Text>{org.name}</Text>
|
||||||
{org.count && (
|
{org.total && (
|
||||||
<>
|
<>
|
||||||
<Tag size="sm" my="auto">
|
<Tag size="sm" my="auto">
|
||||||
{org.count}
|
{org.total}
|
||||||
</Tag>
|
</Tag>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
<PermissionTags permission={collaborator?.permission.value} />
|
<PermissionTags permission={collaborator?.permission.value} />
|
||||||
{org.count && (
|
{org.total && (
|
||||||
<MyIcon
|
<MyIcon
|
||||||
name="core/chat/chevronRight"
|
name="core/chat/chevronRight"
|
||||||
w="16px"
|
w="16px"
|
||||||
@@ -386,11 +370,11 @@ function MemberModal({
|
|||||||
</HStack>
|
</HStack>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return searchText ? (
|
return searchKey ? (
|
||||||
orgs
|
Orgs
|
||||||
) : (
|
) : (
|
||||||
<OrgMemberScrollData>
|
<OrgMemberScrollData>
|
||||||
{orgs}
|
{Orgs}
|
||||||
{orgMembers.map((member) => {
|
{orgMembers.map((member) => {
|
||||||
return (
|
return (
|
||||||
<MemberItemCard
|
<MemberItemCard
|
||||||
@@ -413,29 +397,30 @@ function MemberModal({
|
|||||||
</OrgMemberScrollData>
|
</OrgMemberScrollData>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
{filterGroups?.map((group) => {
|
{(filterClass === 'group' || searchKey) &&
|
||||||
const onChange = () => {
|
groups?.map((group) => {
|
||||||
setSelectedGroupList((state) => {
|
const onChange = () => {
|
||||||
if (state.find((v) => v._id === group._id)) {
|
setSelectedGroupList((state) => {
|
||||||
return state.filter((v) => v._id !== group._id);
|
if (state.find((v) => v._id === group._id)) {
|
||||||
}
|
return state.filter((v) => v._id !== group._id);
|
||||||
return [...state, group];
|
}
|
||||||
});
|
return [...state, group];
|
||||||
};
|
});
|
||||||
const collaborator = collaboratorList?.find((v) => v.groupId === group._id);
|
};
|
||||||
return (
|
const collaborator = collaboratorList?.find((v) => v.groupId === group._id);
|
||||||
<MemberItemCard
|
return (
|
||||||
avatar={group.avatar}
|
<MemberItemCard
|
||||||
key={group._id}
|
avatar={group.avatar}
|
||||||
name={
|
key={group._id}
|
||||||
group.name === DefaultGroupName ? userInfo?.team.teamName ?? '' : group.name
|
name={
|
||||||
}
|
group.name === DefaultGroupName ? userInfo?.team.teamName ?? '' : group.name
|
||||||
permission={collaborator?.permission.value}
|
}
|
||||||
onChange={onChange}
|
permission={collaborator?.permission.value}
|
||||||
isChecked={!!selectedGroupList.find((v) => v._id === group._id)}
|
onChange={onChange}
|
||||||
/>
|
isChecked={!!selectedGroupList.find((v) => v._id === group._id)}
|
||||||
);
|
/>
|
||||||
})}
|
);
|
||||||
|
})}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
@@ -1,34 +1,25 @@
|
|||||||
import {
|
import { Box, ModalBody, Flex, Button, ModalFooter, Grid, HStack } from '@chakra-ui/react';
|
||||||
Box,
|
|
||||||
ModalBody,
|
|
||||||
Flex,
|
|
||||||
Button,
|
|
||||||
ModalFooter,
|
|
||||||
Checkbox,
|
|
||||||
Grid,
|
|
||||||
HStack
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||||
import Tag from '@fastgpt/web/components/common/Tag';
|
import Tag from '@fastgpt/web/components/common/Tag';
|
||||||
|
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import React, { useMemo, useRef, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { putUpdateGroup } from '@/web/support/user/team/group/api';
|
||||||
import { TeamContext } from '../context';
|
|
||||||
import { getGroupMembers, putUpdateGroup } from '@/web/support/user/team/group/api';
|
|
||||||
import { GroupMemberRole } from '@fastgpt/global/support/permission/memberGroup/constant';
|
import { GroupMemberRole } from '@fastgpt/global/support/permission/memberGroup/constant';
|
||||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
import { DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants';
|
import { DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants';
|
||||||
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||||
import {
|
import { MemberGroupListItemType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||||
GroupMemberItemType,
|
import { getTeamMembers } from '@/web/support/user/team/api';
|
||||||
MemberGroupListItemType
|
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
|
||||||
} from '@fastgpt/global/support/permission/memberGroup/type';
|
import { PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||||
import { useMount } from 'ahooks';
|
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import MemberItemCard from '@/components/support/permission/MemberManager/MemberItemCard';
|
||||||
|
|
||||||
export type GroupFormType = {
|
export type GroupFormType = {
|
||||||
members: {
|
members: {
|
||||||
@@ -53,42 +44,65 @@ function GroupEditModal({
|
|||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const allMembers = useContextSelector(TeamContext, (v) => v.members);
|
const [searchKey, setSearchKey] = useState('');
|
||||||
const MemberScrollData = useContextSelector(TeamContext, (v) => v.MemberScrollData);
|
const [selected, setSelected] = useState<
|
||||||
const [hoveredMemberId, setHoveredMemberId] = useState<string>();
|
{ name: string; tmbId: string; avatar: string; role: `${GroupMemberRole}` }[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
const selectedMembersRef = useRef<HTMLDivElement>(null);
|
const {
|
||||||
const [members, setMembers] = useState<GroupMemberItemType[]>([]);
|
data: allMembers = [],
|
||||||
const editGroupId = useMemo(() => {
|
ScrollData: MemberScrollData,
|
||||||
return group?._id;
|
refreshList
|
||||||
}, [group]);
|
} = useScrollPagination<
|
||||||
|
any,
|
||||||
|
PaginationResponse<TeamMemberItemType<{ withOrgs: true; withPermission: true }>>
|
||||||
|
>(getTeamMembers, {
|
||||||
|
pageSize: 20,
|
||||||
|
params: {
|
||||||
|
status: 'active',
|
||||||
|
withOrgs: true,
|
||||||
|
searchKey
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const refetchMemberList = _.debounce(refreshList, 200);
|
||||||
|
|
||||||
useMount(async () => {
|
useEffect(() => refetchMemberList, [searchKey]);
|
||||||
console.log('aaaaaa');
|
|
||||||
if (editGroupId) {
|
const groupId = useMemo(() => String(group._id), [group._id]);
|
||||||
const data = await getGroupMembers(editGroupId);
|
|
||||||
console.log(data);
|
const { data: groupMembers = [], ScrollData: GroupScrollData } = useScrollPagination<
|
||||||
setMembers(data);
|
any,
|
||||||
|
PaginationResponse<
|
||||||
|
TeamMemberItemType<{ withOrgs: true; withPermission: true; withGroupRole: true }>
|
||||||
|
>
|
||||||
|
>(getTeamMembers, {
|
||||||
|
pageSize: 100000,
|
||||||
|
params: {
|
||||||
|
groupId: groupId
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const [searchKey, setSearchKey] = useState('');
|
useEffect(() => {
|
||||||
const filtered = useMemo(() => {
|
if (!groupId) return;
|
||||||
return [
|
setSelected(
|
||||||
...allMembers.filter((member) => {
|
groupMembers.map((item) => ({
|
||||||
if (member.memberName.toLowerCase().includes(searchKey.toLowerCase())) return true;
|
name: item.memberName,
|
||||||
return false;
|
tmbId: item.tmbId,
|
||||||
})
|
avatar: item.avatar,
|
||||||
];
|
role: (item.groupRole ?? 'member') as `${GroupMemberRole}`
|
||||||
}, [searchKey, allMembers]);
|
}))
|
||||||
|
);
|
||||||
|
}, [groupId, groupMembers]);
|
||||||
|
|
||||||
|
const [hoveredMemberId, setHoveredMemberId] = useState<string>();
|
||||||
|
|
||||||
const { runAsync: onUpdate, loading: isLoadingUpdate } = useRequest2(
|
const { runAsync: onUpdate, loading: isLoadingUpdate } = useRequest2(
|
||||||
async () => {
|
async () => {
|
||||||
if (!editGroupId || !members.length) return;
|
if (!group._id || !groupMembers.length) return;
|
||||||
|
|
||||||
return putUpdateGroup({
|
return putUpdateGroup({
|
||||||
groupId: editGroupId,
|
groupId: group._id,
|
||||||
memberList: members
|
memberList: selected
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -97,18 +111,21 @@ function GroupEditModal({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const isSelected = (memberId: string) => {
|
const isSelected = (memberId: string) => {
|
||||||
return members.find((item) => item.tmbId === memberId);
|
return selected.find((item) => item.tmbId === memberId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const myRole = useMemo(() => {
|
const myRole = useMemo(() => {
|
||||||
if (userInfo?.team.permission.hasManagePer) {
|
if (userInfo?.team.permission.hasManagePer) {
|
||||||
return 'owner';
|
return 'owner';
|
||||||
}
|
}
|
||||||
return members.find((item) => item.tmbId === userInfo?.team.tmbId)?.role ?? 'member';
|
return groupMembers.find((item) => item.tmbId === userInfo?.team.tmbId)?.groupRole ?? 'member';
|
||||||
}, [members, userInfo]);
|
}, [groupMembers, userInfo]);
|
||||||
|
|
||||||
const handleToggleSelect = (memberId: string) => {
|
const handleToggleSelect = (memberId: string) => {
|
||||||
if (myRole === 'owner' && memberId === members.find((item) => item.role === 'owner')?.tmbId) {
|
if (
|
||||||
|
myRole === 'owner' &&
|
||||||
|
memberId === groupMembers.find((item) => item.role === 'owner')?.tmbId
|
||||||
|
) {
|
||||||
toast({
|
toast({
|
||||||
title: t('user:team.group.toast.can_not_delete_owner'),
|
title: t('user:team.group.toast.can_not_delete_owner'),
|
||||||
status: 'error'
|
status: 'error'
|
||||||
@@ -118,18 +135,18 @@ function GroupEditModal({
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
myRole === 'admin' &&
|
myRole === 'admin' &&
|
||||||
members.find((item) => String(item.tmbId) === memberId)?.role !== 'member'
|
selected.find((item) => String(item.tmbId) === memberId)?.role !== 'member'
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSelected(memberId)) {
|
if (isSelected(memberId)) {
|
||||||
setMembers(members.filter((item) => item.tmbId !== memberId));
|
setSelected(selected.filter((item) => item.tmbId !== memberId));
|
||||||
} else {
|
} else {
|
||||||
const member = allMembers.find((m) => m.tmbId === memberId);
|
const member = allMembers.find((m) => m.tmbId === memberId);
|
||||||
if (!member) return;
|
if (!member) return;
|
||||||
setMembers([
|
setSelected([
|
||||||
...members,
|
...selected,
|
||||||
{
|
{
|
||||||
name: member.memberName,
|
name: member.memberName,
|
||||||
avatar: member.avatar,
|
avatar: member.avatar,
|
||||||
@@ -142,14 +159,14 @@ function GroupEditModal({
|
|||||||
|
|
||||||
const handleToggleAdmin = (memberId: string) => {
|
const handleToggleAdmin = (memberId: string) => {
|
||||||
if (myRole === 'owner' && isSelected(memberId)) {
|
if (myRole === 'owner' && isSelected(memberId)) {
|
||||||
const oldRole = members.find((item) => item.tmbId === memberId)?.role;
|
const oldRole = groupMembers.find((item) => item.tmbId === memberId)?.groupRole;
|
||||||
if (oldRole === 'admin') {
|
if (oldRole === 'admin') {
|
||||||
setMembers(
|
setSelected(
|
||||||
members.map((item) => (item.tmbId === memberId ? { ...item, role: 'member' } : item))
|
selected.map((item) => (item.tmbId === memberId ? { ...item, role: 'member' } : item))
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
setMembers(
|
setSelected(
|
||||||
members.map((item) => (item.tmbId === memberId ? { ...item, role: 'admin' } : item))
|
selected.map((item) => (item.tmbId === memberId ? { ...item, role: 'admin' } : item))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,37 +201,24 @@ function GroupEditModal({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<MemberScrollData mt={3} flexGrow="1" overflow={'auto'}>
|
<MemberScrollData mt={3} flexGrow="1" overflow={'auto'}>
|
||||||
{filtered.map((member) => {
|
{allMembers.map((member) => {
|
||||||
return (
|
return (
|
||||||
<HStack
|
<MemberItemCard
|
||||||
py="2"
|
avatar={member.avatar}
|
||||||
px={3}
|
|
||||||
borderRadius={'md'}
|
|
||||||
alignItems="center"
|
|
||||||
key={member.tmbId}
|
key={member.tmbId}
|
||||||
cursor={'pointer'}
|
name={member.memberName}
|
||||||
_hover={{
|
onChange={() => handleToggleSelect(member.tmbId)}
|
||||||
bg: 'myGray.50',
|
isChecked={!!isSelected(member.tmbId)}
|
||||||
...(!isSelected(member.tmbId) ? { svg: { color: 'myGray.50' } } : {})
|
orgs={member.orgs}
|
||||||
}}
|
/>
|
||||||
_notLast={{ mb: 2 }}
|
|
||||||
onClick={() => handleToggleSelect(member.tmbId)}
|
|
||||||
>
|
|
||||||
<Checkbox
|
|
||||||
isChecked={!!isSelected(member.tmbId)}
|
|
||||||
icon={<MyIcon name={'common/check'} w={'12px'} />}
|
|
||||||
/>
|
|
||||||
<Avatar src={member.avatar} w="1.5rem" borderRadius={'50%'} />
|
|
||||||
<Box>{member.memberName}</Box>
|
|
||||||
</HStack>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</MemberScrollData>
|
</MemberScrollData>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex borderLeft="1px" borderColor="myGray.200" flexDirection="column" p="4" h={'100%'}>
|
<Flex borderLeft="1px" borderColor="myGray.200" flexDirection="column" p="4" h={'100%'}>
|
||||||
<Box mt={2}>{t('common:chosen') + ': ' + members.length}</Box>
|
<Box mt={2}>{t('common:chosen') + ': ' + selected.length}</Box>
|
||||||
<MemberScrollData ScrollContainerRef={selectedMembersRef} mt={3} flex={'1 0 0'} h={0}>
|
<GroupScrollData mt={3} flex={'1 0 0'} h={0}>
|
||||||
{members.map((member) => {
|
{selected.map((member) => {
|
||||||
return (
|
return (
|
||||||
<HStack
|
<HStack
|
||||||
onMouseEnter={() => setHoveredMemberId(member.tmbId)}
|
onMouseEnter={() => setHoveredMemberId(member.tmbId)}
|
||||||
@@ -284,7 +288,7 @@ function GroupEditModal({
|
|||||||
</HStack>
|
</HStack>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</MemberScrollData>
|
</GroupScrollData>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
@@ -15,12 +15,16 @@ import Avatar from '@fastgpt/web/components/common/Avatar';
|
|||||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import React, { useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { TeamContext } from '../context';
|
import { TeamContext } from '../context';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import { MemberGroupListItemType } from '@fastgpt/global/support/permission/memberGroup/type';
|
import { MemberGroupListItemType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||||
import { GetSearchUserGroupOrg } from '@/web/support/user/api';
|
import { GetSearchUserGroupOrg } from '@/web/support/user/api';
|
||||||
import { Omit } from '@fastgpt/web/components/common/DndDrag';
|
import { Omit } from '@fastgpt/web/components/common/DndDrag';
|
||||||
|
import { getTeamMembers } from '@/web/support/user/team/api';
|
||||||
|
import { PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||||
|
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
export function ChangeOwnerModal({
|
export function ChangeOwnerModal({
|
||||||
group,
|
group,
|
||||||
@@ -33,22 +37,24 @@ export function ChangeOwnerModal({
|
|||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [inputValue, setInputValue] = React.useState('');
|
const [searchKey, setSearchKey] = React.useState('');
|
||||||
const { data: searchedData } = useRequest2(
|
|
||||||
async () => {
|
const {
|
||||||
if (!inputValue) return;
|
data: members = [],
|
||||||
return GetSearchUserGroupOrg(inputValue);
|
ScrollData: MemberScrollData,
|
||||||
},
|
refreshList
|
||||||
|
} = useScrollPagination<any, PaginationResponse<TeamMemberItemType<{ withGroupRole: true }>>>(
|
||||||
|
getTeamMembers,
|
||||||
{
|
{
|
||||||
manual: false,
|
pageSize: 20,
|
||||||
refreshDeps: [inputValue],
|
params: {
|
||||||
throttleWait: 500,
|
searchKey: searchKey
|
||||||
debounceWait: 200
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const { members: allMembers } = useContextSelector(TeamContext, (v) => v);
|
const search = _.debounce(refreshList, 500);
|
||||||
const memberList = searchedData ? searchedData.members : allMembers;
|
useEffect(() => search, [searchKey]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOpen: isOpenMemberListMenu,
|
isOpen: isOpenMemberListMenu,
|
||||||
@@ -108,9 +114,9 @@ export function ChangeOwnerModal({
|
|||||||
)}
|
)}
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('common:permission.change_owner_placeholder')}
|
placeholder={t('common:permission.change_owner_placeholder')}
|
||||||
value={inputValue}
|
value={searchKey}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setInputValue(e.target.value);
|
setSearchKey(e.target.value);
|
||||||
setSelectedMember(null);
|
setSelectedMember(null);
|
||||||
}}
|
}}
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
@@ -120,7 +126,7 @@ export function ChangeOwnerModal({
|
|||||||
{...(selectedMember && { pl: '10' })}
|
{...(selectedMember && { pl: '10' })}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
{isOpenMemberListMenu && memberList.length > 0 && (
|
{isOpenMemberListMenu && members.length > 0 && (
|
||||||
<Flex
|
<Flex
|
||||||
mt={2}
|
mt={2}
|
||||||
w={'100%'}
|
w={'100%'}
|
||||||
@@ -134,26 +140,28 @@ export function ChangeOwnerModal({
|
|||||||
maxH={'300px'}
|
maxH={'300px'}
|
||||||
overflow={'auto'}
|
overflow={'auto'}
|
||||||
>
|
>
|
||||||
{memberList.map((item) => (
|
<MemberScrollData>
|
||||||
<Box
|
{members.map((item) => (
|
||||||
key={item.tmbId}
|
<Box
|
||||||
p="2"
|
key={item.tmbId}
|
||||||
_hover={{ bg: 'myGray.100' }}
|
p="2"
|
||||||
mx="1"
|
_hover={{ bg: 'myGray.100' }}
|
||||||
borderRadius="md"
|
mx="1"
|
||||||
cursor={'pointer'}
|
borderRadius="md"
|
||||||
onClickCapture={() => {
|
cursor={'pointer'}
|
||||||
setInputValue(item.memberName);
|
onClickCapture={() => {
|
||||||
setSelectedMember(item);
|
setSearchKey(item.memberName);
|
||||||
onCloseMemberListMenu();
|
setSelectedMember(item);
|
||||||
}}
|
onCloseMemberListMenu();
|
||||||
>
|
}}
|
||||||
<Flex align="center">
|
>
|
||||||
<Avatar src={item.avatar} w="1.25rem" />
|
<Flex align="center">
|
||||||
<Box ml="2">{item.memberName}</Box>
|
<Avatar src={item.avatar} w="1.25rem" />
|
||||||
</Flex>
|
<Box ml="2">{item.memberName}</Box>
|
||||||
</Box>
|
</Flex>
|
||||||
))}
|
</Box>
|
||||||
|
))}
|
||||||
|
</MemberScrollData>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@@ -41,12 +41,15 @@ import {
|
|||||||
import format from 'date-fns/format';
|
import format from 'date-fns/format';
|
||||||
import OrgTags from '@/components/support/user/team/OrgTags';
|
import OrgTags from '@/components/support/user/team/OrgTags';
|
||||||
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { downloadFetch } from '@/web/common/system/utils';
|
import { downloadFetch } from '@/web/common/system/utils';
|
||||||
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
|
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
|
||||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||||
|
import { PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||||
|
|
||||||
const InviteModal = dynamic(() => import('./Invite/InviteModal'));
|
const InviteModal = dynamic(() => import('./Invite/InviteModal'));
|
||||||
const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal'));
|
const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal'));
|
||||||
@@ -55,11 +58,26 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const statusOptions = [
|
||||||
|
{
|
||||||
|
label: t('common:common.All'),
|
||||||
|
value: undefined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('common:user.team.member.active'),
|
||||||
|
value: 'active'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('account_team:leave'),
|
||||||
|
value: 'inactive'
|
||||||
|
}
|
||||||
|
];
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
const { feConfigs } = useSystemStore();
|
const { feConfigs } = useSystemStore();
|
||||||
const isSyncMember = feConfigs?.register_method?.includes('sync');
|
const isSyncMember = feConfigs?.register_method?.includes('sync');
|
||||||
|
|
||||||
const { myTeams, onSwitchTeam } = useContextSelector(TeamContext, (v) => v);
|
const { myTeams, onSwitchTeam } = useContextSelector(TeamContext, (v) => v);
|
||||||
|
const [status, setStatus] = useState<string>();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOpen: isOpenTeamTagsAsync,
|
isOpen: isOpenTeamTagsAsync,
|
||||||
@@ -68,35 +86,36 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
} = useDisclosure();
|
} = useDisclosure();
|
||||||
|
|
||||||
// member action
|
// member action
|
||||||
|
const [searchKey, setSearchKey] = useState<string>('');
|
||||||
const {
|
const {
|
||||||
data: members = [],
|
data: members = [],
|
||||||
isLoading: loadingMembers,
|
isLoading: loadingMembers,
|
||||||
refreshList: refetchMemberList,
|
refreshList: refetchMemberList,
|
||||||
ScrollData: MemberScrollData
|
ScrollData: MemberScrollData
|
||||||
} = useScrollPagination(getTeamMembers, {
|
} = useScrollPagination<
|
||||||
|
any,
|
||||||
|
PaginationResponse<TeamMemberItemType<{ withOrgs: true; withPermission: true }>>
|
||||||
|
>(getTeamMembers, {
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
params: {
|
params: {
|
||||||
withLeaved: true
|
status,
|
||||||
|
withPermission: true,
|
||||||
|
withOrgs: true,
|
||||||
|
searchKey
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const [searchText, setSearchText] = useState<string>('');
|
const refreshList = _.debounce(() => {
|
||||||
const { data: searchMembersData, run: refreshSearchMembers } = useRequest2(
|
refetchMemberList();
|
||||||
async () => {
|
}, 200);
|
||||||
if (!searchText) return Promise.resolve();
|
|
||||||
return GetSearchUserGroupOrg(searchText, { members: true, orgs: false, groups: false });
|
useEffect(() => {
|
||||||
},
|
refreshList();
|
||||||
{
|
}, [searchKey, status]);
|
||||||
manual: false,
|
|
||||||
throttleWait: 500,
|
|
||||||
refreshDeps: [searchText]
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const onRefreshMembers = useCallback(() => {
|
const onRefreshMembers = useCallback(() => {
|
||||||
refetchMemberList();
|
refetchMemberList();
|
||||||
refreshSearchMembers();
|
}, [refetchMemberList]);
|
||||||
}, [refetchMemberList, refreshSearchMembers]);
|
|
||||||
|
|
||||||
const { isOpen: isOpenInvite, onOpen: onOpenInvite, onClose: onCloseInvite } = useDisclosure();
|
const { isOpen: isOpenInvite, onOpen: onOpenInvite, onClose: onCloseInvite } = useDisclosure();
|
||||||
|
|
||||||
@@ -166,10 +185,13 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
|
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
|
||||||
{Tabs}
|
{Tabs}
|
||||||
<HStack alignItems={'center'}>
|
<HStack alignItems={'center'}>
|
||||||
|
<Box>
|
||||||
|
<MySelect list={statusOptions} value={status} onChange={(v) => setStatus(v)} />
|
||||||
|
</Box>
|
||||||
<Box width={'200px'}>
|
<Box width={'200px'}>
|
||||||
<SearchInput
|
<SearchInput
|
||||||
placeholder={t('account_team:search_member')}
|
placeholder={t('account_team:search_member')}
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
onChange={(e) => setSearchKey(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
{userInfo?.team.permission.hasManagePer && feConfigs?.show_team_chat && (
|
{userInfo?.team.permission.hasManagePer && feConfigs?.show_team_chat && (
|
||||||
@@ -264,107 +286,104 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
</Tr>
|
</Tr>
|
||||||
</Thead>
|
</Thead>
|
||||||
<Tbody>
|
<Tbody>
|
||||||
{(searchText && searchMembersData ? searchMembersData.members : members).map(
|
{members.map((member) => (
|
||||||
(member) => (
|
<Tr key={member.tmbId} overflow={'unset'}>
|
||||||
<Tr key={member.tmbId} overflow={'unset'}>
|
<Td>
|
||||||
<Td>
|
<HStack>
|
||||||
<HStack>
|
<Avatar src={member.avatar} w={['18px', '22px']} borderRadius={'50%'} />
|
||||||
<Avatar src={member.avatar} w={['18px', '22px']} borderRadius={'50%'} />
|
<Box className={'textEllipsis'}>
|
||||||
<Box className={'textEllipsis'}>
|
{member.memberName}
|
||||||
{member.memberName}
|
{member.status !== 'active' && (
|
||||||
{member.status !== 'active' && (
|
<Tag ml="2" colorSchema="gray" bg={'myGray.100'} color={'myGray.700'}>
|
||||||
<Tag ml="2" colorSchema="gray" bg={'myGray.100'} color={'myGray.700'}>
|
{t('account_team:leave')}
|
||||||
{t('account_team:leave')}
|
</Tag>
|
||||||
</Tag>
|
)}
|
||||||
)}
|
</Box>
|
||||||
</Box>
|
</HStack>
|
||||||
</HStack>
|
</Td>
|
||||||
</Td>
|
<Td maxW={'300px'}>{member.contact || '-'}</Td>
|
||||||
<Td maxW={'300px'}>{member.contact || '-'}</Td>
|
<Td maxWidth="300px">
|
||||||
<Td maxWidth="300px">
|
{(() => {
|
||||||
{(() => {
|
return <OrgTags orgs={member.orgs || undefined} type="tag" />;
|
||||||
return <OrgTags orgs={member.orgs || undefined} type="tag" />;
|
})()}
|
||||||
})()}
|
</Td>
|
||||||
</Td>
|
<Td maxW={'300px'}>
|
||||||
<Td maxW={'300px'}>
|
<VStack gap={0}>
|
||||||
<VStack gap={0} alignItems="flex-start">
|
<Box>{format(new Date(member.createTime), 'yyyy-MM-dd HH:mm:ss')}</Box>
|
||||||
<Box>{format(new Date(member.createTime), 'yyyy-MM-dd HH:mm:ss')}</Box>
|
<Box>
|
||||||
<Box>
|
{member.updateTime
|
||||||
{member.updateTime
|
? format(new Date(member.updateTime), 'yyyy-MM-dd HH:mm:ss')
|
||||||
? format(new Date(member.updateTime), 'yyyy-MM-dd HH:mm:ss')
|
: '-'}
|
||||||
: '-'}
|
</Box>
|
||||||
</Box>
|
</VStack>
|
||||||
</VStack>
|
</Td>
|
||||||
</Td>
|
<Td>
|
||||||
<Td>
|
{userInfo?.team.permission.hasManagePer &&
|
||||||
{userInfo?.team.permission.hasManagePer &&
|
member.role !== TeamMemberRoleEnum.owner &&
|
||||||
member.role !== TeamMemberRoleEnum.owner &&
|
member.tmbId !== userInfo?.team.tmbId &&
|
||||||
member.tmbId !== userInfo?.team.tmbId &&
|
(member.status === TeamMemberStatusEnum.active ? (
|
||||||
(member.status === TeamMemberStatusEnum.active ? (
|
<>
|
||||||
<>
|
{' '}
|
||||||
<Icon
|
<Icon
|
||||||
name={'edit'}
|
name={'edit'}
|
||||||
cursor={'pointer'}
|
cursor={'pointer'}
|
||||||
w="1rem"
|
w="1rem"
|
||||||
p="1"
|
p="1"
|
||||||
borderRadius="sm"
|
borderRadius="sm"
|
||||||
_hover={{
|
_hover={{
|
||||||
color: 'blue.600',
|
color: 'blue.600',
|
||||||
bgColor: 'myGray.100'
|
bgColor: 'myGray.100'
|
||||||
}}
|
}}
|
||||||
onClick={() =>
|
onClick={() => handleEditMemberName(member.tmbId, member.memberName)}
|
||||||
handleEditMemberName(member.tmbId, member.memberName)
|
/>
|
||||||
}
|
<Icon
|
||||||
/>
|
name={'common/trash'}
|
||||||
<Icon
|
cursor={'pointer'}
|
||||||
name={'common/trash'}
|
w="1rem"
|
||||||
cursor={'pointer'}
|
p="1"
|
||||||
w="1rem"
|
borderRadius="sm"
|
||||||
p="1"
|
_hover={{
|
||||||
borderRadius="sm"
|
color: 'red.600',
|
||||||
_hover={{
|
bgColor: 'myGray.100'
|
||||||
color: 'red.600',
|
}}
|
||||||
bgColor: 'myGray.100'
|
onClick={() => {
|
||||||
}}
|
openRemoveMember(
|
||||||
onClick={() => {
|
() => onRemoveMember(member.tmbId),
|
||||||
openRemoveMember(
|
undefined,
|
||||||
() => onRemoveMember(member.tmbId),
|
t('account_team:remove_tip', {
|
||||||
undefined,
|
username: member.memberName
|
||||||
t('account_team:remove_tip', {
|
})
|
||||||
username: member.memberName
|
)();
|
||||||
})
|
}}
|
||||||
)();
|
/>
|
||||||
}}
|
</>
|
||||||
/>
|
) : (
|
||||||
</>
|
member.status === TeamMemberStatusEnum.forbidden && (
|
||||||
) : (
|
<Icon
|
||||||
member.status === TeamMemberStatusEnum.forbidden && (
|
name={'common/confirm/restoreTip'}
|
||||||
<Icon
|
cursor={'pointer'}
|
||||||
name={'common/confirm/restoreTip'}
|
w="1rem"
|
||||||
cursor={'pointer'}
|
p="1"
|
||||||
w="1rem"
|
borderRadius="sm"
|
||||||
p="1"
|
_hover={{
|
||||||
borderRadius="sm"
|
color: 'primary.500',
|
||||||
_hover={{
|
bgColor: 'myGray.100'
|
||||||
color: 'primary.500',
|
}}
|
||||||
bgColor: 'myGray.100'
|
onClick={() => {
|
||||||
}}
|
openRestoreMember(
|
||||||
onClick={() => {
|
() => onRestore(member.tmbId),
|
||||||
openRestoreMember(
|
undefined,
|
||||||
() => onRestore(member.tmbId),
|
t('account_team:restore_tip', {
|
||||||
undefined,
|
username: member.memberName
|
||||||
t('account_team:restore_tip', {
|
})
|
||||||
username: member.memberName
|
)();
|
||||||
})
|
}}
|
||||||
)();
|
/>
|
||||||
}}
|
)
|
||||||
/>
|
))}
|
||||||
)
|
</Td>
|
||||||
))}
|
</Tr>
|
||||||
</Td>
|
))}
|
||||||
</Tr>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Tbody>
|
</Tbody>
|
||||||
</Table>
|
</Table>
|
||||||
<ConfirmRemoveMemberModal />
|
<ConfirmRemoveMemberModal />
|
||||||
|
@@ -29,12 +29,14 @@ function OrgInfoModal({
|
|||||||
editOrg,
|
editOrg,
|
||||||
onClose,
|
onClose,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
updateCurrentOrg
|
updateCurrentOrg,
|
||||||
|
parentId
|
||||||
}: {
|
}: {
|
||||||
editOrg: OrgFormType;
|
editOrg: OrgFormType;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSuccess: () => void;
|
onSuccess: () => void;
|
||||||
updateCurrentOrg: (data: { name?: string; avatar?: string; description?: string }) => void;
|
updateCurrentOrg: (data: { name?: string; avatar?: string; description?: string }) => void;
|
||||||
|
parentId?: string;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -51,10 +53,11 @@ function OrgInfoModal({
|
|||||||
|
|
||||||
const { run: onCreate, loading: isLoadingCreate } = useRequest2(
|
const { run: onCreate, loading: isLoadingCreate } = useRequest2(
|
||||||
async (data: OrgFormType) => {
|
async (data: OrgFormType) => {
|
||||||
|
if (parentId === undefined) return;
|
||||||
return postCreateOrg({
|
return postCreateOrg({
|
||||||
name: data.name,
|
name: data.name,
|
||||||
avatar: data.avatar,
|
avatar: data.avatar,
|
||||||
path: editOrg.path,
|
orgId: parentId,
|
||||||
description: data.description
|
description: data.description
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@@ -1,14 +1,5 @@
|
|||||||
import { getOrgMembers, putUpdateOrgMembers } from '@/web/support/user/team/org/api';
|
import { putUpdateOrgMembers } from '@/web/support/user/team/org/api';
|
||||||
import {
|
import { Box, Button, Flex, Grid, HStack, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Checkbox,
|
|
||||||
Flex,
|
|
||||||
Grid,
|
|
||||||
HStack,
|
|
||||||
ModalBody,
|
|
||||||
ModalFooter
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import type { GroupMemberRole } from '@fastgpt/global/support/permission/memberGroup/constant';
|
import type { GroupMemberRole } from '@fastgpt/global/support/permission/memberGroup/constant';
|
||||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
@@ -17,11 +8,11 @@ import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
|||||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { OrgListItemType } from '@fastgpt/global/support/user/team/org/type';
|
import { OrgListItemType } from '@fastgpt/global/support/user/team/org/type';
|
||||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||||
import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant';
|
|
||||||
import { getTeamMembers } from '@/web/support/user/team/api';
|
import { getTeamMembers } from '@/web/support/user/team/api';
|
||||||
|
import MemberItemCard from '@/components/support/permission/MemberManager/MemberItemCard';
|
||||||
|
|
||||||
export type GroupFormType = {
|
export type GroupFormType = {
|
||||||
members: {
|
members: {
|
||||||
@@ -30,16 +21,6 @@ export type GroupFormType = {
|
|||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
function CheckboxIcon({
|
|
||||||
name
|
|
||||||
}: {
|
|
||||||
isChecked?: boolean;
|
|
||||||
isIndeterminate?: boolean;
|
|
||||||
name: IconNameType;
|
|
||||||
}) {
|
|
||||||
return <MyIcon name={name} w="12px" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function OrgMemberManageModal({
|
function OrgMemberManageModal({
|
||||||
currentOrg,
|
currentOrg,
|
||||||
refetchOrgs,
|
refetchOrgs,
|
||||||
@@ -56,17 +37,24 @@ function OrgMemberManageModal({
|
|||||||
ScrollData: MemberScrollData,
|
ScrollData: MemberScrollData,
|
||||||
isLoading: isLoadingMembers
|
isLoading: isLoadingMembers
|
||||||
} = useScrollPagination(getTeamMembers, {
|
} = useScrollPagination(getTeamMembers, {
|
||||||
pageSize: 20
|
pageSize: 20,
|
||||||
|
params: {
|
||||||
|
withOrgs: true,
|
||||||
|
withPermission: false,
|
||||||
|
status: 'active'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: orgMembers,
|
data: orgMembers,
|
||||||
ScrollData: OrgMemberScrollData,
|
ScrollData: OrgMemberScrollData,
|
||||||
isLoading: isLoadingOrgMembers
|
isLoading: isLoadingOrgMembers
|
||||||
} = useScrollPagination(getOrgMembers, {
|
} = useScrollPagination(getTeamMembers, {
|
||||||
pageSize: 20,
|
pageSize: 100000,
|
||||||
params: {
|
params: {
|
||||||
orgPath: getOrgChildrenPath(currentOrg)
|
orgId: currentOrg._id,
|
||||||
|
withOrgs: false,
|
||||||
|
withPermission: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -83,11 +71,6 @@ function OrgMemberManageModal({
|
|||||||
}, [orgMembers]);
|
}, [orgMembers]);
|
||||||
|
|
||||||
const [searchKey, setSearchKey] = useState('');
|
const [searchKey, setSearchKey] = useState('');
|
||||||
const filterMembers = useMemo(() => {
|
|
||||||
if (!searchKey) return allMembers;
|
|
||||||
const regx = new RegExp(searchKey, 'i');
|
|
||||||
return allMembers.filter((member) => regx.test(member.memberName));
|
|
||||||
}, [searchKey, allMembers]);
|
|
||||||
|
|
||||||
const { run: onUpdate, loading: isLoadingUpdate } = useRequest2(
|
const { run: onUpdate, loading: isLoadingUpdate } = useRequest2(
|
||||||
() => {
|
() => {
|
||||||
@@ -165,35 +148,20 @@ function OrgMemberManageModal({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<MemberScrollData mt={3} flexGrow="1" overflow={'auto'} isLoading={isLoadingMembers}>
|
<MemberScrollData mt={3} flexGrow="1" overflow={'auto'} isLoading={isLoadingMembers}>
|
||||||
{filterMembers.map((member) => {
|
{allMembers.map((member) => {
|
||||||
return (
|
return (
|
||||||
<HStack
|
<MemberItemCard
|
||||||
py="2"
|
avatar={member.avatar}
|
||||||
px={3}
|
|
||||||
borderRadius={'md'}
|
|
||||||
alignItems="center"
|
|
||||||
key={member.tmbId}
|
key={member.tmbId}
|
||||||
cursor={'pointer'}
|
name={member.memberName}
|
||||||
_hover={{
|
onChange={() => handleToggleSelect(member.tmbId)}
|
||||||
bg: 'myGray.50',
|
isChecked={!!isSelected(member.tmbId)}
|
||||||
...(!isSelected(member.tmbId) ? { svg: { color: 'myGray.50' } } : {})
|
orgs={member.orgs}
|
||||||
}}
|
/>
|
||||||
_notLast={{ mb: 2 }}
|
|
||||||
onClick={() => handleToggleSelect(member.tmbId)}
|
|
||||||
>
|
|
||||||
<Checkbox
|
|
||||||
isChecked={!!isSelected(member.tmbId)}
|
|
||||||
icon={<CheckboxIcon name={'common/check'} />}
|
|
||||||
pointerEvents="none"
|
|
||||||
/>
|
|
||||||
<Avatar src={member.avatar} w="1.5rem" borderRadius={'50%'} />
|
|
||||||
<Box>{member.memberName}</Box>
|
|
||||||
</HStack>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</MemberScrollData>
|
</MemberScrollData>
|
||||||
</Flex>
|
</Flex>
|
||||||
{/* <Flex mt={3} flexDirection="column" flexGrow="1" overflow={'auto'} maxH={'100%'}> */}
|
|
||||||
<Flex flexDirection="column" p="4" overflowY="auto" overflowX="hidden">
|
<Flex flexDirection="column" p="4" overflowY="auto" overflowX="hidden">
|
||||||
<OrgMemberScrollData
|
<OrgMemberScrollData
|
||||||
mt={3}
|
mt={3}
|
||||||
|
@@ -20,8 +20,6 @@ function OrgMoveModal({
|
|||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [selectedOrg, setSelectedOrg] = useState<OrgListItemType>();
|
const [selectedOrg, setSelectedOrg] = useState<OrgListItemType>();
|
||||||
const { userInfo } = useUserStore();
|
|
||||||
const team = userInfo?.team!;
|
|
||||||
|
|
||||||
const { runAsync: onMoveOrg, loading } = useRequest2(putMoveOrg, {
|
const { runAsync: onMoveOrg, loading } = useRequest2(putMoveOrg, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -39,7 +37,7 @@ function OrgMoveModal({
|
|||||||
iconColor="primary.600"
|
iconColor="primary.600"
|
||||||
>
|
>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<OrgTree selectedOrg={selectedOrg} setSelectedOrg={setSelectedOrg} />
|
<OrgTree selectedOrg={selectedOrg} setSelectedOrg={setSelectedOrg} movingOrg={movingOrg} />
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button
|
<Button
|
||||||
|
@@ -14,17 +14,19 @@ function OrgTreeNode({
|
|||||||
org,
|
org,
|
||||||
selectedOrg,
|
selectedOrg,
|
||||||
setSelectedOrg,
|
setSelectedOrg,
|
||||||
index = 0
|
index = 0,
|
||||||
|
movingOrg
|
||||||
}: {
|
}: {
|
||||||
org: OrgListItemType;
|
org: OrgListItemType;
|
||||||
selectedOrg?: OrgListItemType;
|
selectedOrg?: OrgListItemType;
|
||||||
setSelectedOrg: (org?: OrgListItemType) => void;
|
setSelectedOrg: (org?: OrgListItemType) => void;
|
||||||
index?: number;
|
index?: number;
|
||||||
|
movingOrg: OrgListItemType;
|
||||||
}) {
|
}) {
|
||||||
const [isExpanded, toggleIsExpanded] = useToggle(index === 0);
|
const [isExpanded, toggleIsExpanded] = useToggle(index === 0);
|
||||||
const [canBeExpanded, setCanBeExpanded] = useState(true);
|
const [canBeExpanded, setCanBeExpanded] = useState(true);
|
||||||
const { data: orgs = [], runAsync: getOrgs } = useRequest2(() =>
|
const { data: orgs = [], runAsync: getOrgs } = useRequest2(() =>
|
||||||
getOrgList({ orgPath: getOrgChildrenPath(org) })
|
getOrgList({ orgId: org._id, withPermission: false })
|
||||||
);
|
);
|
||||||
const onClickExpand = async () => {
|
const onClickExpand = async () => {
|
||||||
const data = await getOrgs();
|
const data = await getOrgs();
|
||||||
@@ -34,6 +36,9 @@ function OrgTreeNode({
|
|||||||
toggleIsExpanded.toggle();
|
toggleIsExpanded.toggle();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (org._id === movingOrg._id) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Box userSelect={'none'}>
|
<Box userSelect={'none'}>
|
||||||
<HStack
|
<HStack
|
||||||
@@ -78,6 +83,7 @@ function OrgTreeNode({
|
|||||||
orgs.map((child) => (
|
orgs.map((child) => (
|
||||||
<Box key={child._id} mt={0.5}>
|
<Box key={child._id} mt={0.5}>
|
||||||
<OrgTreeNode
|
<OrgTreeNode
|
||||||
|
movingOrg={movingOrg}
|
||||||
org={child}
|
org={child}
|
||||||
index={index + 1}
|
index={index + 1}
|
||||||
selectedOrg={selectedOrg}
|
selectedOrg={selectedOrg}
|
||||||
@@ -91,10 +97,12 @@ function OrgTreeNode({
|
|||||||
|
|
||||||
function OrgTree({
|
function OrgTree({
|
||||||
selectedOrg,
|
selectedOrg,
|
||||||
setSelectedOrg
|
setSelectedOrg,
|
||||||
|
movingOrg
|
||||||
}: {
|
}: {
|
||||||
selectedOrg?: OrgListItemType;
|
selectedOrg?: OrgListItemType;
|
||||||
setSelectedOrg: (org?: OrgListItemType) => void;
|
setSelectedOrg: (org?: OrgListItemType) => void;
|
||||||
|
movingOrg: OrgListItemType;
|
||||||
}) {
|
}) {
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
const root: OrgListItemType = {
|
const root: OrgListItemType = {
|
||||||
@@ -107,6 +115,7 @@ function OrgTree({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<OrgTreeNode
|
<OrgTreeNode
|
||||||
|
movingOrg={movingOrg}
|
||||||
key={'root'}
|
key={'root'}
|
||||||
org={root}
|
org={root}
|
||||||
selectedOrg={selectedOrg}
|
selectedOrg={selectedOrg}
|
||||||
|
@@ -78,9 +78,6 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
const [editOrg, setEditOrg] = useState<OrgFormType>();
|
const [editOrg, setEditOrg] = useState<OrgFormType>();
|
||||||
const [manageMemberOrg, setManageMemberOrg] = useState<OrgListItemType>();
|
const [manageMemberOrg, setManageMemberOrg] = useState<OrgListItemType>();
|
||||||
const [movingOrg, setMovingOrg] = useState<OrgListItemType>();
|
const [movingOrg, setMovingOrg] = useState<OrgListItemType>();
|
||||||
|
|
||||||
const [searchOrg, setSearchOrg] = useState('');
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
currentOrg,
|
currentOrg,
|
||||||
orgs,
|
orgs,
|
||||||
@@ -91,7 +88,9 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
MemberScrollData,
|
MemberScrollData,
|
||||||
onPathClick,
|
onPathClick,
|
||||||
refresh,
|
refresh,
|
||||||
updateCurrentOrg
|
updateCurrentOrg,
|
||||||
|
setSearchKey,
|
||||||
|
searchKey
|
||||||
} = useOrg();
|
} = useOrg();
|
||||||
|
|
||||||
// Delete org
|
// Delete org
|
||||||
@@ -123,12 +122,6 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
onSuccess: refresh
|
onSuccess: refresh
|
||||||
});
|
});
|
||||||
|
|
||||||
const searchedOrgs = useMemo(() => {
|
|
||||||
if (!searchOrg) return [];
|
|
||||||
|
|
||||||
return orgs.filter((org) => org.name.includes(searchOrg));
|
|
||||||
}, [orgs, searchOrg]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
|
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
|
||||||
@@ -136,8 +129,8 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
<Box w="200px">
|
<Box w="200px">
|
||||||
<SearchInput
|
<SearchInput
|
||||||
placeholder={t('account_team:search_org')}
|
placeholder={t('account_team:search_org')}
|
||||||
value={searchOrg}
|
value={searchKey}
|
||||||
onChange={(e) => setSearchOrg(e.target.value)}
|
onChange={(e) => setSearchKey(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -166,102 +159,55 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
</Tr>
|
</Tr>
|
||||||
</Thead>
|
</Thead>
|
||||||
<Tbody>
|
<Tbody>
|
||||||
{searchedOrgs.map((org) => (
|
{orgs
|
||||||
<Tr key={org._id} overflow={'unset'} onClick={() => onClickOrg(org)}>
|
.filter((org) => org.path !== '')
|
||||||
<Td>
|
.map((org) => (
|
||||||
<HStack cursor={'pointer'} onClick={() => onClickOrg(org)}>
|
<Tr key={org._id} overflow={'unset'}>
|
||||||
<MemberTag name={org.name} avatar={org.avatar!} />
|
<Td>
|
||||||
<Tag size="sm">{org.total}</Tag>
|
<HStack cursor={'pointer'} onClick={() => onClickOrg(org)}>
|
||||||
<MyIcon
|
<MemberTag name={org.name} avatar={org.avatar} />
|
||||||
name="core/chat/chevronRight"
|
<Tag size="sm">{org.total}</Tag>
|
||||||
w={'1rem'}
|
<MyIcon
|
||||||
h={'1rem'}
|
name="core/chat/chevronRight"
|
||||||
color={'myGray.500'}
|
w={'1rem'}
|
||||||
/>
|
h={'1rem'}
|
||||||
</HStack>
|
color={'myGray.500'}
|
||||||
</Td>
|
/>
|
||||||
{isTeamAdmin && !isSyncMember && (
|
</HStack>
|
||||||
<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>
|
</Td>
|
||||||
)}
|
{isTeamAdmin && !isSyncMember && (
|
||||||
</Tr>
|
<Td w={'6rem'}>
|
||||||
))}
|
<MyMenu
|
||||||
{!searchOrg &&
|
trigger="hover"
|
||||||
orgs
|
Button={<IconButton name="more" />}
|
||||||
.filter((org) => org.path !== '')
|
menuList={[
|
||||||
.map((org) => (
|
{
|
||||||
<Tr key={org._id} overflow={'unset'}>
|
children: [
|
||||||
<Td>
|
{
|
||||||
<HStack cursor={'pointer'} onClick={() => onClickOrg(org)}>
|
icon: 'edit',
|
||||||
<MemberTag name={org.name} avatar={org.avatar} />
|
label: t('account_team:edit_info'),
|
||||||
<Tag size="sm">{org.total}</Tag>
|
onClick: () => setEditOrg(org)
|
||||||
<MyIcon
|
},
|
||||||
name="core/chat/chevronRight"
|
{
|
||||||
w={'1rem'}
|
icon: 'common/file/move',
|
||||||
h={'1rem'}
|
label: t('common:Move'),
|
||||||
color={'myGray.500'}
|
onClick: () => setMovingOrg(org)
|
||||||
/>
|
},
|
||||||
</HStack>
|
{
|
||||||
|
icon: 'delete',
|
||||||
|
label: t('account_team:delete'),
|
||||||
|
type: 'danger',
|
||||||
|
onClick: () => deleteOrgHandler(org._id)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</Td>
|
</Td>
|
||||||
{isTeamAdmin && !isSyncMember && (
|
)}
|
||||||
<Td w={'6rem'}>
|
</Tr>
|
||||||
<MyMenu
|
))}
|
||||||
trigger="hover"
|
{!searchKey &&
|
||||||
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>
|
|
||||||
))}
|
|
||||||
{!searchOrg &&
|
|
||||||
members.map((member) => {
|
members.map((member) => {
|
||||||
return (
|
return (
|
||||||
<Tr key={member.tmbId}>
|
<Tr key={member.tmbId}>
|
||||||
@@ -403,6 +349,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
onClose={() => setEditOrg(undefined)}
|
onClose={() => setEditOrg(undefined)}
|
||||||
onSuccess={refresh}
|
onSuccess={refresh}
|
||||||
updateCurrentOrg={updateCurrentOrg}
|
updateCurrentOrg={updateCurrentOrg}
|
||||||
|
parentId={currentOrg._id}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!!movingOrg && (
|
{!!movingOrg && (
|
||||||
|
@@ -20,20 +20,17 @@ const EditInfoModal = dynamic(() => import('./EditInfoModal'));
|
|||||||
|
|
||||||
type TeamModalContextType = {
|
type TeamModalContextType = {
|
||||||
myTeams: TeamTmbItemType[];
|
myTeams: TeamTmbItemType[];
|
||||||
members: TeamMemberItemType[];
|
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
onSwitchTeam: (teamId: string) => void;
|
onSwitchTeam: (teamId: string) => void;
|
||||||
setEditTeamData: React.Dispatch<React.SetStateAction<EditTeamFormDataType | undefined>>;
|
setEditTeamData: React.Dispatch<React.SetStateAction<EditTeamFormDataType | undefined>>;
|
||||||
|
|
||||||
refetchMembers: () => void;
|
refetchTeamSize: () => void;
|
||||||
refetchTeams: () => void;
|
refetchTeams: () => void;
|
||||||
teamSize: number;
|
teamSize: number;
|
||||||
MemberScrollData: ReturnType<typeof useScrollPagination>['ScrollData'];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TeamContext = createContext<TeamModalContextType>({
|
export const TeamContext = createContext<TeamModalContextType>({
|
||||||
myTeams: [],
|
myTeams: [],
|
||||||
members: [],
|
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
onSwitchTeam: function (_teamId: string): void {
|
onSwitchTeam: function (_teamId: string): void {
|
||||||
throw new Error('Function not implemented.');
|
throw new Error('Function not implemented.');
|
||||||
@@ -44,11 +41,10 @@ export const TeamContext = createContext<TeamModalContextType>({
|
|||||||
refetchTeams: function (): void {
|
refetchTeams: function (): void {
|
||||||
throw new Error('Function not implemented.');
|
throw new Error('Function not implemented.');
|
||||||
},
|
},
|
||||||
refetchMembers: function (): void {
|
refetchTeamSize: function (): void {
|
||||||
throw new Error('Function not implemented.');
|
throw new Error('Function not implemented.');
|
||||||
},
|
},
|
||||||
teamSize: 0,
|
teamSize: 0
|
||||||
MemberScrollData: () => <></>
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const TeamModalContextProvider = ({ children }: { children: ReactNode }) => {
|
export const TeamModalContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
@@ -67,32 +63,11 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
|
|||||||
refreshDeps: [userInfo?._id]
|
refreshDeps: [userInfo?._id]
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: teamMemberCountData, refresh: refetchTeamMemberCount } = useRequest2(
|
const { data: teamMemberCountData, refresh: refetchTeamSize } = useRequest2(getTeamMemberCount, {
|
||||||
getTeamMemberCount,
|
manual: false,
|
||||||
{
|
refreshDeps: [userInfo?.team?.teamId]
|
||||||
manual: false,
|
|
||||||
refreshDeps: [userInfo?.team?.teamId]
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// member action
|
|
||||||
const {
|
|
||||||
data: members = [],
|
|
||||||
isLoading: loadingMembers,
|
|
||||||
refreshList: refetchMemberList,
|
|
||||||
ScrollData: MemberScrollData
|
|
||||||
} = useScrollPagination(getTeamMembers, {
|
|
||||||
pageSize: 20,
|
|
||||||
params: {
|
|
||||||
withLeaved: true
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const refetchMembers = useCallback(() => {
|
|
||||||
refetchTeamMemberCount();
|
|
||||||
refetchMemberList();
|
|
||||||
}, [refetchTeamMemberCount, refetchMemberList]);
|
|
||||||
|
|
||||||
const { runAsync: onSwitchTeam, loading: isSwitchingTeam } = useRequest2(
|
const { runAsync: onSwitchTeam, loading: isSwitchingTeam } = useRequest2(
|
||||||
async (teamId: string) => {
|
async (teamId: string) => {
|
||||||
await putSwitchTeam(teamId);
|
await putSwitchTeam(teamId);
|
||||||
@@ -106,7 +81,7 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const isLoading = isLoadingTeams || isSwitchingTeam || loadingMembers;
|
const isLoading = isLoadingTeams || isSwitchingTeam;
|
||||||
|
|
||||||
const contextValue = {
|
const contextValue = {
|
||||||
myTeams,
|
myTeams,
|
||||||
@@ -116,10 +91,8 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
|
|||||||
|
|
||||||
// create | update team
|
// create | update team
|
||||||
setEditTeamData,
|
setEditTeamData,
|
||||||
members,
|
|
||||||
refetchMembers,
|
|
||||||
teamSize: teamMemberCountData?.count || 0,
|
teamSize: teamMemberCountData?.count || 0,
|
||||||
MemberScrollData
|
refetchTeamSize
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -34,8 +34,16 @@ export const putSwitchTeam = (teamId: string) =>
|
|||||||
PUT<string>(`/proApi/support/user/team/switch`, { teamId });
|
PUT<string>(`/proApi/support/user/team/switch`, { teamId });
|
||||||
|
|
||||||
/* --------------- team member ---------------- */
|
/* --------------- team member ---------------- */
|
||||||
export const getTeamMembers = (props: PaginationProps<{ withLeaved?: boolean }>) =>
|
export const getTeamMembers = (
|
||||||
GET<PaginationResponse<TeamMemberItemType>>(`/proApi/support/user/team/member/list`, props);
|
props: PaginationProps<{
|
||||||
|
status?: 'active' | 'inactive';
|
||||||
|
withOrgs?: boolean;
|
||||||
|
withPermission?: boolean;
|
||||||
|
searchKey?: string;
|
||||||
|
orgId?: string;
|
||||||
|
groupId?: string;
|
||||||
|
}>
|
||||||
|
) => POST<PaginationResponse<TeamMemberItemType>>(`/proApi/support/user/team/member/list`, props);
|
||||||
export const getTeamMemberCount = () =>
|
export const getTeamMemberCount = () =>
|
||||||
GET<{ count: number }>(`/proApi/support/user/team/member/count`);
|
GET<{ count: number }>(`/proApi/support/user/team/member/count`);
|
||||||
|
|
||||||
|
@@ -10,8 +10,11 @@ import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/t
|
|||||||
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
|
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
|
||||||
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||||
|
|
||||||
export const getOrgList = (params: { orgPath: string; getPermission?: boolean }) =>
|
export const getOrgList = (params: {
|
||||||
GET<OrgListItemType[]>(`/proApi/support/user/team/org/list`, params);
|
orgId: string;
|
||||||
|
withPermission?: boolean;
|
||||||
|
searchKey?: string;
|
||||||
|
}) => POST<OrgListItemType[]>(`/proApi/support/user/team/org/list`, params);
|
||||||
|
|
||||||
export const postCreateOrg = (data: postCreateOrgData) =>
|
export const postCreateOrg = (data: postCreateOrgData) =>
|
||||||
POST('/proApi/support/user/team/org/create', data);
|
POST('/proApi/support/user/team/org/create', data);
|
||||||
|
@@ -1,14 +1,17 @@
|
|||||||
import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant';
|
import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant';
|
||||||
import { OrgListItemType } from '@fastgpt/global/support/user/team/org/type';
|
import { OrgListItemType } from '@fastgpt/global/support/user/team/org/type';
|
||||||
import { memo, useMemo, useState } from 'react';
|
import { memo, useEffect, useMemo, useState } from 'react';
|
||||||
import { useUserStore } from '../../../useUserStore';
|
import { useUserStore } from '../../../useUserStore';
|
||||||
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
|
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
|
||||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { getOrgList, getOrgMembers } from '../api';
|
import { getOrgList, getOrgMembers } from '../api';
|
||||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||||
|
import { getTeamMembers } from '../../api';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
function useOrg({ getPermission = true }: { getPermission?: boolean } = {}) {
|
function useOrg({ withPermission = true }: { withPermission?: boolean } = {}) {
|
||||||
const [orgStack, setOrgStack] = useState<OrgListItemType[]>([]);
|
const [orgStack, setOrgStack] = useState<OrgListItemType[]>([]);
|
||||||
|
const [searchKey, setSearchKey] = useState('');
|
||||||
|
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
|
|
||||||
@@ -34,10 +37,18 @@ function useOrg({ getPermission = true }: { getPermission?: boolean } = {}) {
|
|||||||
data: orgs = [],
|
data: orgs = [],
|
||||||
loading: isLoadingOrgs,
|
loading: isLoadingOrgs,
|
||||||
refresh: refetchOrgs
|
refresh: refetchOrgs
|
||||||
} = useRequest2(() => getOrgList({ orgPath: path, getPermission }), {
|
} = useRequest2(
|
||||||
manual: false,
|
() => getOrgList({ orgId: currentOrg._id, withPermission: withPermission, searchKey }),
|
||||||
refreshDeps: [userInfo?.team?.teamId, path]
|
{
|
||||||
});
|
manual: false,
|
||||||
|
refreshDeps: [userInfo?.team?.teamId, path, currentOrg._id]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const search = _.debounce(() => {
|
||||||
|
if (!searchKey) return;
|
||||||
|
refetchOrgs();
|
||||||
|
}, 200);
|
||||||
|
useEffect(() => search, [searchKey]);
|
||||||
|
|
||||||
const paths = useMemo(() => {
|
const paths = useMemo(() => {
|
||||||
if (!currentOrg) return [];
|
if (!currentOrg) return [];
|
||||||
@@ -53,16 +64,20 @@ function useOrg({ getPermission = true }: { getPermission?: boolean } = {}) {
|
|||||||
|
|
||||||
const onClickOrg = (org: OrgListItemType) => {
|
const onClickOrg = (org: OrgListItemType) => {
|
||||||
setOrgStack([...orgStack, org]);
|
setOrgStack([...orgStack, org]);
|
||||||
|
setSearchKey('');
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: members = [],
|
data: members = [],
|
||||||
ScrollData: MemberScrollData,
|
ScrollData: MemberScrollData,
|
||||||
refreshList: refetchMembers
|
refreshList: refetchMembers
|
||||||
} = useScrollPagination(getOrgMembers, {
|
} = useScrollPagination(getTeamMembers, {
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
params: {
|
params: {
|
||||||
orgPath: path
|
orgId: currentOrg._id,
|
||||||
|
withOrgs: false,
|
||||||
|
withPermission: true,
|
||||||
|
status: 'active'
|
||||||
},
|
},
|
||||||
refreshDeps: [path]
|
refreshDeps: [path]
|
||||||
});
|
});
|
||||||
@@ -70,6 +85,7 @@ function useOrg({ getPermission = true }: { getPermission?: boolean } = {}) {
|
|||||||
const onPathClick = (path: string) => {
|
const onPathClick = (path: string) => {
|
||||||
const pathIds = path.split('/');
|
const pathIds = path.split('/');
|
||||||
setOrgStack(orgStack.filter((org) => pathIds.includes(org.pathId)));
|
setOrgStack(orgStack.filter((org) => pathIds.includes(org.pathId)));
|
||||||
|
setSearchKey('');
|
||||||
};
|
};
|
||||||
|
|
||||||
const refresh = () => {
|
const refresh = () => {
|
||||||
@@ -101,7 +117,9 @@ function useOrg({ getPermission = true }: { getPermission?: boolean } = {}) {
|
|||||||
MemberScrollData,
|
MemberScrollData,
|
||||||
onPathClick,
|
onPathClick,
|
||||||
refresh,
|
refresh,
|
||||||
updateCurrentOrg
|
updateCurrentOrg,
|
||||||
|
searchKey,
|
||||||
|
setSearchKey
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user