pref: member list (#4344)

* chore: search member new api

* chore: permission

* fix: ts error

* fix: member modal
This commit is contained in:
Finley Ge
2025-03-26 22:10:03 +08:00
committed by archer
parent 484b87478c
commit 2ebb2ccc9c
15 changed files with 508 additions and 548 deletions

View File

@@ -1,9 +1,8 @@
// orgId, pathid, path === null ===> root org
export type postCreateOrgData = {
name: string;
description?: string;
avatar?: string;
path?: string;
orgId?: string;
};
export type putUpdateOrgMembersData = {

View File

@@ -70,7 +70,13 @@ export type TeamTmbItemType = {
permission: TeamPermission;
} & ThirdPartyAccountType;
export type TeamMemberItemType = {
export type TeamMemberItemType<
Options extends {
withPermission?: boolean;
withOrgs?: boolean;
withGroupRole?: boolean;
} = { withPermission: true; withOrgs: true; withGroupRole: false }
> = {
userId: string;
tmbId: string;
teamId: string;
@@ -78,12 +84,24 @@ export type TeamMemberItemType = {
avatar: string;
role: `${TeamMemberRoleEnum}`;
status: `${TeamMemberStatusEnum}`;
permission: TeamPermission;
contact?: string;
createTime: 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 = {
label: string;

View File

@@ -19,7 +19,7 @@ import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useTranslation } from 'next-i18next';
import { useMemo, useRef, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import PermissionSelect from './PermissionSelect';
import PermissionTags from './PermissionTags';
import {
@@ -39,6 +39,7 @@ import { GetSearchUserGroupOrg } from '@/web/support/user/api';
import useOrg from '@/web/support/user/team/org/hooks/useOrg';
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
import { MemberGroupListItemType } from '@fastgpt/global/support/permission/memberGroup/type';
import _ from 'lodash';
const HoverBoxStyle = {
bgColor: 'myGray.50',
@@ -55,7 +56,6 @@ function MemberModal({
const { t } = useTranslation();
const { userInfo } = useUserStore();
const collaboratorList = useContextSelector(CollaboratorContext, (v) => v.collaboratorList);
const [searchText, setSearchText] = useState<string>('');
const [filterClass, setFilterClass] = useState<'member' | 'org' | 'group'>();
const {
paths,
@@ -63,18 +63,35 @@ function MemberModal({
members: orgMembers,
MemberScrollData: OrgMemberScrollData,
onPathClick,
orgs
} = useOrg({ getPermission: false });
orgs,
searchKey,
setSearchKey
} = useOrg({ withPermission: false });
const { data: members, ScrollData: TeamMemberScrollData } = useScrollPagination(getTeamMembers, {
pageSize: 15
const {
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 () => {
if (!userInfo?.team?.teamId) return [];
return getGroupList<false>({
withMembers: false
withMembers: false,
searchKey
});
},
{
@@ -83,53 +100,20 @@ function MemberModal({
}
);
const { data: searchedData } = useRequest2(() => GetSearchUserGroupOrg(searchText), {
manual: false,
throttleWait: 500,
debounceWait: 200,
refreshDeps: [searchText]
});
const search = _.debounce(() => {
refreshList();
refreshGroups();
}, 200);
useEffect(search, [searchKey]);
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<
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 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 getPerLabelList = useContextSelector(CollaboratorContext, (v) => v.getPerLabelList);
const [selectedPermission, setSelectedPermission] = useState<number | undefined>(
@@ -225,12 +209,12 @@ function MemberModal({
<SearchInput
placeholder={t('user:search_group_org_user')}
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}>
{/* Entry */}
{!searchText && !filterClass && (
{!searchKey && !filterClass && (
<>
{entryList.current.map((item) => {
return (
@@ -257,7 +241,7 @@ function MemberModal({
)}
{/* Path */}
{!searchText && filterClass && (
{!searchKey && filterClass && (
<Box mb={1}>
<Path
paths={[
@@ -291,9 +275,9 @@ function MemberModal({
/>
</Box>
)}
{(filterClass === 'member' || (searchText && filterMembers.length > 0)) &&
{(filterClass === 'member' || searchKey) &&
(() => {
const members = filterMembers?.map((member) => {
const Members = members?.map((member) => {
const onChange = () => {
setSelectedMemberList((state) => {
if (state.find((v) => v.tmbId === member.tmbId)) {
@@ -315,8 +299,8 @@ function MemberModal({
/>
);
});
return searchText ? (
members
return searchKey ? (
Members
) : (
<TeamMemberScrollData
flexDirection={'column'}
@@ -324,13 +308,13 @@ function MemberModal({
userSelect={'none'}
height={'fit-content'}
>
{members}
{Members}
</TeamMemberScrollData>
);
})()}
{(filterClass === 'org' || searchText) &&
{(filterClass === 'org' || searchKey) &&
(() => {
const orgs = filterOrgs?.map((org) => {
const Orgs = orgs?.map((org) => {
const onChange = () => {
setSelectedOrgIdList((state) => {
if (state.find((v) => v._id === org._id)) {
@@ -356,18 +340,18 @@ function MemberModal({
pointerEvents="none"
/>
<MyAvatar src={org.avatar} w="1.5rem" borderRadius={'50%'} />
<HStack ml="2" w="full" gap="5px">
<HStack w="full">
<Text>{org.name}</Text>
{org.count && (
{org.total && (
<>
<Tag size="sm" my="auto">
{org.count}
{org.total}
</Tag>
</>
)}
</HStack>
<PermissionTags permission={collaborator?.permission.value} />
{org.count && (
{org.total && (
<MyIcon
name="core/chat/chevronRight"
w="16px"
@@ -386,11 +370,11 @@ function MemberModal({
</HStack>
);
});
return searchText ? (
orgs
return searchKey ? (
Orgs
) : (
<OrgMemberScrollData>
{orgs}
{Orgs}
{orgMembers.map((member) => {
return (
<MemberItemCard
@@ -413,29 +397,30 @@ function MemberModal({
</OrgMemberScrollData>
);
})()}
{filterGroups?.map((group) => {
const onChange = () => {
setSelectedGroupList((state) => {
if (state.find((v) => v._id === group._id)) {
return state.filter((v) => v._id !== group._id);
}
return [...state, group];
});
};
const collaborator = collaboratorList?.find((v) => v.groupId === group._id);
return (
<MemberItemCard
avatar={group.avatar}
key={group._id}
name={
group.name === DefaultGroupName ? userInfo?.team.teamName ?? '' : group.name
}
permission={collaborator?.permission.value}
onChange={onChange}
isChecked={!!selectedGroupList.find((v) => v._id === group._id)}
/>
);
})}
{(filterClass === 'group' || searchKey) &&
groups?.map((group) => {
const onChange = () => {
setSelectedGroupList((state) => {
if (state.find((v) => v._id === group._id)) {
return state.filter((v) => v._id !== group._id);
}
return [...state, group];
});
};
const collaborator = collaboratorList?.find((v) => v.groupId === group._id);
return (
<MemberItemCard
avatar={group.avatar}
key={group._id}
name={
group.name === DefaultGroupName ? userInfo?.team.teamName ?? '' : group.name
}
permission={collaborator?.permission.value}
onChange={onChange}
isChecked={!!selectedGroupList.find((v) => v._id === group._id)}
/>
);
})}
</Flex>
</Flex>

View File

@@ -1,34 +1,25 @@
import {
Box,
ModalBody,
Flex,
Button,
ModalFooter,
Checkbox,
Grid,
HStack
} from '@chakra-ui/react';
import { Box, ModalBody, Flex, Button, ModalFooter, Grid, HStack } from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import MyIcon from '@fastgpt/web/components/common/Icon';
import Avatar from '@fastgpt/web/components/common/Avatar';
import Tag from '@fastgpt/web/components/common/Tag';
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 { useContextSelector } from 'use-context-selector';
import { TeamContext } from '../context';
import { getGroupMembers, putUpdateGroup } from '@/web/support/user/team/group/api';
import { putUpdateGroup } from '@/web/support/user/team/group/api';
import { GroupMemberRole } from '@fastgpt/global/support/permission/memberGroup/constant';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants';
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
import {
GroupMemberItemType,
MemberGroupListItemType
} from '@fastgpt/global/support/permission/memberGroup/type';
import { useMount } from 'ahooks';
import { MemberGroupListItemType } from '@fastgpt/global/support/permission/memberGroup/type';
import { getTeamMembers } from '@/web/support/user/team/api';
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
import { PaginationResponse } from '@fastgpt/web/common/fetch/type';
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
import _ from 'lodash';
import MemberItemCard from '@/components/support/permission/MemberManager/MemberItemCard';
export type GroupFormType = {
members: {
@@ -53,42 +44,65 @@ function GroupEditModal({
const { userInfo } = useUserStore();
const { toast } = useToast();
const allMembers = useContextSelector(TeamContext, (v) => v.members);
const MemberScrollData = useContextSelector(TeamContext, (v) => v.MemberScrollData);
const [hoveredMemberId, setHoveredMemberId] = useState<string>();
const [searchKey, setSearchKey] = useState('');
const [selected, setSelected] = useState<
{ name: string; tmbId: string; avatar: string; role: `${GroupMemberRole}` }[]
>([]);
const selectedMembersRef = useRef<HTMLDivElement>(null);
const [members, setMembers] = useState<GroupMemberItemType[]>([]);
const editGroupId = useMemo(() => {
return group?._id;
}, [group]);
const {
data: allMembers = [],
ScrollData: MemberScrollData,
refreshList
} = useScrollPagination<
any,
PaginationResponse<TeamMemberItemType<{ withOrgs: true; withPermission: true }>>
>(getTeamMembers, {
pageSize: 20,
params: {
status: 'active',
withOrgs: true,
searchKey
}
});
const refetchMemberList = _.debounce(refreshList, 200);
useMount(async () => {
console.log('aaaaaa');
if (editGroupId) {
const data = await getGroupMembers(editGroupId);
console.log(data);
setMembers(data);
useEffect(() => refetchMemberList, [searchKey]);
const groupId = useMemo(() => String(group._id), [group._id]);
const { data: groupMembers = [], ScrollData: GroupScrollData } = useScrollPagination<
any,
PaginationResponse<
TeamMemberItemType<{ withOrgs: true; withPermission: true; withGroupRole: true }>
>
>(getTeamMembers, {
pageSize: 100000,
params: {
groupId: groupId
}
});
const [searchKey, setSearchKey] = useState('');
const filtered = useMemo(() => {
return [
...allMembers.filter((member) => {
if (member.memberName.toLowerCase().includes(searchKey.toLowerCase())) return true;
return false;
})
];
}, [searchKey, allMembers]);
useEffect(() => {
if (!groupId) return;
setSelected(
groupMembers.map((item) => ({
name: item.memberName,
tmbId: item.tmbId,
avatar: item.avatar,
role: (item.groupRole ?? 'member') as `${GroupMemberRole}`
}))
);
}, [groupId, groupMembers]);
const [hoveredMemberId, setHoveredMemberId] = useState<string>();
const { runAsync: onUpdate, loading: isLoadingUpdate } = useRequest2(
async () => {
if (!editGroupId || !members.length) return;
if (!group._id || !groupMembers.length) return;
return putUpdateGroup({
groupId: editGroupId,
memberList: members
groupId: group._id,
memberList: selected
});
},
{
@@ -97,18 +111,21 @@ function GroupEditModal({
);
const isSelected = (memberId: string) => {
return members.find((item) => item.tmbId === memberId);
return selected.find((item) => item.tmbId === memberId);
};
const myRole = useMemo(() => {
if (userInfo?.team.permission.hasManagePer) {
return 'owner';
}
return members.find((item) => item.tmbId === userInfo?.team.tmbId)?.role ?? 'member';
}, [members, userInfo]);
return groupMembers.find((item) => item.tmbId === userInfo?.team.tmbId)?.groupRole ?? 'member';
}, [groupMembers, userInfo]);
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({
title: t('user:team.group.toast.can_not_delete_owner'),
status: 'error'
@@ -118,18 +135,18 @@ function GroupEditModal({
if (
myRole === 'admin' &&
members.find((item) => String(item.tmbId) === memberId)?.role !== 'member'
selected.find((item) => String(item.tmbId) === memberId)?.role !== 'member'
) {
return;
}
if (isSelected(memberId)) {
setMembers(members.filter((item) => item.tmbId !== memberId));
setSelected(selected.filter((item) => item.tmbId !== memberId));
} else {
const member = allMembers.find((m) => m.tmbId === memberId);
if (!member) return;
setMembers([
...members,
setSelected([
...selected,
{
name: member.memberName,
avatar: member.avatar,
@@ -142,14 +159,14 @@ function GroupEditModal({
const handleToggleAdmin = (memberId: string) => {
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') {
setMembers(
members.map((item) => (item.tmbId === memberId ? { ...item, role: 'member' } : item))
setSelected(
selected.map((item) => (item.tmbId === memberId ? { ...item, role: 'member' } : item))
);
} else {
setMembers(
members.map((item) => (item.tmbId === memberId ? { ...item, role: 'admin' } : item))
setSelected(
selected.map((item) => (item.tmbId === memberId ? { ...item, role: 'admin' } : item))
);
}
}
@@ -184,37 +201,24 @@ function GroupEditModal({
}}
/>
<MemberScrollData mt={3} flexGrow="1" overflow={'auto'}>
{filtered.map((member) => {
{allMembers.map((member) => {
return (
<HStack
py="2"
px={3}
borderRadius={'md'}
alignItems="center"
<MemberItemCard
avatar={member.avatar}
key={member.tmbId}
cursor={'pointer'}
_hover={{
bg: 'myGray.50',
...(!isSelected(member.tmbId) ? { svg: { color: 'myGray.50' } } : {})
}}
_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>
name={member.memberName}
onChange={() => handleToggleSelect(member.tmbId)}
isChecked={!!isSelected(member.tmbId)}
orgs={member.orgs}
/>
);
})}
</MemberScrollData>
</Flex>
<Flex borderLeft="1px" borderColor="myGray.200" flexDirection="column" p="4" h={'100%'}>
<Box mt={2}>{t('common:chosen') + ': ' + members.length}</Box>
<MemberScrollData ScrollContainerRef={selectedMembersRef} mt={3} flex={'1 0 0'} h={0}>
{members.map((member) => {
<Box mt={2}>{t('common:chosen') + ': ' + selected.length}</Box>
<GroupScrollData mt={3} flex={'1 0 0'} h={0}>
{selected.map((member) => {
return (
<HStack
onMouseEnter={() => setHoveredMemberId(member.tmbId)}
@@ -284,7 +288,7 @@ function GroupEditModal({
</HStack>
);
})}
</MemberScrollData>
</GroupScrollData>
</Flex>
</Grid>
</ModalBody>

View File

@@ -15,12 +15,16 @@ import Avatar from '@fastgpt/web/components/common/Avatar';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useTranslation } from 'next-i18next';
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { TeamContext } from '../context';
import { useContextSelector } from 'use-context-selector';
import { MemberGroupListItemType } from '@fastgpt/global/support/permission/memberGroup/type';
import { GetSearchUserGroupOrg } from '@/web/support/user/api';
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({
group,
@@ -33,22 +37,24 @@ export function ChangeOwnerModal({
}) {
const { t } = useTranslation();
const [inputValue, setInputValue] = React.useState('');
const { data: searchedData } = useRequest2(
async () => {
if (!inputValue) return;
return GetSearchUserGroupOrg(inputValue);
},
const [searchKey, setSearchKey] = React.useState('');
const {
data: members = [],
ScrollData: MemberScrollData,
refreshList
} = useScrollPagination<any, PaginationResponse<TeamMemberItemType<{ withGroupRole: true }>>>(
getTeamMembers,
{
manual: false,
refreshDeps: [inputValue],
throttleWait: 500,
debounceWait: 200
pageSize: 20,
params: {
searchKey: searchKey
}
}
);
const { members: allMembers } = useContextSelector(TeamContext, (v) => v);
const memberList = searchedData ? searchedData.members : allMembers;
const search = _.debounce(refreshList, 500);
useEffect(() => search, [searchKey]);
const {
isOpen: isOpenMemberListMenu,
@@ -108,9 +114,9 @@ export function ChangeOwnerModal({
)}
<Input
placeholder={t('common:permission.change_owner_placeholder')}
value={inputValue}
value={searchKey}
onChange={(e) => {
setInputValue(e.target.value);
setSearchKey(e.target.value);
setSelectedMember(null);
}}
onFocus={() => {
@@ -120,7 +126,7 @@ export function ChangeOwnerModal({
{...(selectedMember && { pl: '10' })}
/>
</Flex>
{isOpenMemberListMenu && memberList.length > 0 && (
{isOpenMemberListMenu && members.length > 0 && (
<Flex
mt={2}
w={'100%'}
@@ -134,26 +140,28 @@ export function ChangeOwnerModal({
maxH={'300px'}
overflow={'auto'}
>
{memberList.map((item) => (
<Box
key={item.tmbId}
p="2"
_hover={{ bg: 'myGray.100' }}
mx="1"
borderRadius="md"
cursor={'pointer'}
onClickCapture={() => {
setInputValue(item.memberName);
setSelectedMember(item);
onCloseMemberListMenu();
}}
>
<Flex align="center">
<Avatar src={item.avatar} w="1.25rem" />
<Box ml="2">{item.memberName}</Box>
</Flex>
</Box>
))}
<MemberScrollData>
{members.map((item) => (
<Box
key={item.tmbId}
p="2"
_hover={{ bg: 'myGray.100' }}
mx="1"
borderRadius="md"
cursor={'pointer'}
onClickCapture={() => {
setSearchKey(item.memberName);
setSelectedMember(item);
onCloseMemberListMenu();
}}
>
<Flex align="center">
<Avatar src={item.avatar} w="1.25rem" />
<Box ml="2">{item.memberName}</Box>
</Flex>
</Box>
))}
</MemberScrollData>
</Flex>
)}

View File

@@ -41,12 +41,15 @@ import {
import format from 'date-fns/format';
import OrgTags from '@/components/support/user/team/OrgTags';
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 { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
import { useToast } from '@fastgpt/web/hooks/useToast';
import MyBox from '@fastgpt/web/components/common/MyBox';
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 TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal'));
@@ -55,11 +58,26 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
const { t } = useTranslation();
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 { feConfigs } = useSystemStore();
const isSyncMember = feConfigs?.register_method?.includes('sync');
const { myTeams, onSwitchTeam } = useContextSelector(TeamContext, (v) => v);
const [status, setStatus] = useState<string>();
const {
isOpen: isOpenTeamTagsAsync,
@@ -68,35 +86,36 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
} = useDisclosure();
// member action
const [searchKey, setSearchKey] = useState<string>('');
const {
data: members = [],
isLoading: loadingMembers,
refreshList: refetchMemberList,
ScrollData: MemberScrollData
} = useScrollPagination(getTeamMembers, {
} = useScrollPagination<
any,
PaginationResponse<TeamMemberItemType<{ withOrgs: true; withPermission: true }>>
>(getTeamMembers, {
pageSize: 20,
params: {
withLeaved: true
status,
withPermission: true,
withOrgs: true,
searchKey
}
});
const [searchText, setSearchText] = useState<string>('');
const { data: searchMembersData, run: refreshSearchMembers } = useRequest2(
async () => {
if (!searchText) return Promise.resolve();
return GetSearchUserGroupOrg(searchText, { members: true, orgs: false, groups: false });
},
{
manual: false,
throttleWait: 500,
refreshDeps: [searchText]
}
);
const refreshList = _.debounce(() => {
refetchMemberList();
}, 200);
useEffect(() => {
refreshList();
}, [searchKey, status]);
const onRefreshMembers = useCallback(() => {
refetchMemberList();
refreshSearchMembers();
}, [refetchMemberList, refreshSearchMembers]);
}, [refetchMemberList]);
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'}>
{Tabs}
<HStack alignItems={'center'}>
<Box>
<MySelect list={statusOptions} value={status} onChange={(v) => setStatus(v)} />
</Box>
<Box width={'200px'}>
<SearchInput
placeholder={t('account_team:search_member')}
onChange={(e) => setSearchText(e.target.value)}
onChange={(e) => setSearchKey(e.target.value)}
/>
</Box>
{userInfo?.team.permission.hasManagePer && feConfigs?.show_team_chat && (
@@ -264,107 +286,104 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
</Tr>
</Thead>
<Tbody>
{(searchText && searchMembersData ? searchMembersData.members : members).map(
(member) => (
<Tr key={member.tmbId} overflow={'unset'}>
<Td>
<HStack>
<Avatar src={member.avatar} w={['18px', '22px']} borderRadius={'50%'} />
<Box className={'textEllipsis'}>
{member.memberName}
{member.status !== 'active' && (
<Tag ml="2" colorSchema="gray" bg={'myGray.100'} color={'myGray.700'}>
{t('account_team:leave')}
</Tag>
)}
</Box>
</HStack>
</Td>
<Td maxW={'300px'}>{member.contact || '-'}</Td>
<Td maxWidth="300px">
{(() => {
return <OrgTags orgs={member.orgs || undefined} type="tag" />;
})()}
</Td>
<Td maxW={'300px'}>
<VStack gap={0} alignItems="flex-start">
<Box>{format(new Date(member.createTime), 'yyyy-MM-dd HH:mm:ss')}</Box>
<Box>
{member.updateTime
? format(new Date(member.updateTime), 'yyyy-MM-dd HH:mm:ss')
: '-'}
</Box>
</VStack>
</Td>
<Td>
{userInfo?.team.permission.hasManagePer &&
member.role !== TeamMemberRoleEnum.owner &&
member.tmbId !== userInfo?.team.tmbId &&
(member.status === TeamMemberStatusEnum.active ? (
<>
<Icon
name={'edit'}
cursor={'pointer'}
w="1rem"
p="1"
borderRadius="sm"
_hover={{
color: 'blue.600',
bgColor: 'myGray.100'
}}
onClick={() =>
handleEditMemberName(member.tmbId, member.memberName)
}
/>
<Icon
name={'common/trash'}
cursor={'pointer'}
w="1rem"
p="1"
borderRadius="sm"
_hover={{
color: 'red.600',
bgColor: 'myGray.100'
}}
onClick={() => {
openRemoveMember(
() => onRemoveMember(member.tmbId),
undefined,
t('account_team:remove_tip', {
username: member.memberName
})
)();
}}
/>
</>
) : (
member.status === TeamMemberStatusEnum.forbidden && (
<Icon
name={'common/confirm/restoreTip'}
cursor={'pointer'}
w="1rem"
p="1"
borderRadius="sm"
_hover={{
color: 'primary.500',
bgColor: 'myGray.100'
}}
onClick={() => {
openRestoreMember(
() => onRestore(member.tmbId),
undefined,
t('account_team:restore_tip', {
username: member.memberName
})
)();
}}
/>
)
))}
</Td>
</Tr>
)
)}
{members.map((member) => (
<Tr key={member.tmbId} overflow={'unset'}>
<Td>
<HStack>
<Avatar src={member.avatar} w={['18px', '22px']} borderRadius={'50%'} />
<Box className={'textEllipsis'}>
{member.memberName}
{member.status !== 'active' && (
<Tag ml="2" colorSchema="gray" bg={'myGray.100'} color={'myGray.700'}>
{t('account_team:leave')}
</Tag>
)}
</Box>
</HStack>
</Td>
<Td maxW={'300px'}>{member.contact || '-'}</Td>
<Td maxWidth="300px">
{(() => {
return <OrgTags orgs={member.orgs || undefined} type="tag" />;
})()}
</Td>
<Td maxW={'300px'}>
<VStack gap={0}>
<Box>{format(new Date(member.createTime), 'yyyy-MM-dd HH:mm:ss')}</Box>
<Box>
{member.updateTime
? format(new Date(member.updateTime), 'yyyy-MM-dd HH:mm:ss')
: '-'}
</Box>
</VStack>
</Td>
<Td>
{userInfo?.team.permission.hasManagePer &&
member.role !== TeamMemberRoleEnum.owner &&
member.tmbId !== userInfo?.team.tmbId &&
(member.status === TeamMemberStatusEnum.active ? (
<>
{' '}
<Icon
name={'edit'}
cursor={'pointer'}
w="1rem"
p="1"
borderRadius="sm"
_hover={{
color: 'blue.600',
bgColor: 'myGray.100'
}}
onClick={() => handleEditMemberName(member.tmbId, member.memberName)}
/>
<Icon
name={'common/trash'}
cursor={'pointer'}
w="1rem"
p="1"
borderRadius="sm"
_hover={{
color: 'red.600',
bgColor: 'myGray.100'
}}
onClick={() => {
openRemoveMember(
() => onRemoveMember(member.tmbId),
undefined,
t('account_team:remove_tip', {
username: member.memberName
})
)();
}}
/>
</>
) : (
member.status === TeamMemberStatusEnum.forbidden && (
<Icon
name={'common/confirm/restoreTip'}
cursor={'pointer'}
w="1rem"
p="1"
borderRadius="sm"
_hover={{
color: 'primary.500',
bgColor: 'myGray.100'
}}
onClick={() => {
openRestoreMember(
() => onRestore(member.tmbId),
undefined,
t('account_team:restore_tip', {
username: member.memberName
})
)();
}}
/>
)
))}
</Td>
</Tr>
))}
</Tbody>
</Table>
<ConfirmRemoveMemberModal />

View File

@@ -29,12 +29,14 @@ function OrgInfoModal({
editOrg,
onClose,
onSuccess,
updateCurrentOrg
updateCurrentOrg,
parentId
}: {
editOrg: OrgFormType;
onClose: () => void;
onSuccess: () => void;
updateCurrentOrg: (data: { name?: string; avatar?: string; description?: string }) => void;
parentId?: string;
}) {
const { t } = useTranslation();
@@ -51,10 +53,11 @@ function OrgInfoModal({
const { run: onCreate, loading: isLoadingCreate } = useRequest2(
async (data: OrgFormType) => {
if (parentId === undefined) return;
return postCreateOrg({
name: data.name,
avatar: data.avatar,
path: editOrg.path,
orgId: parentId,
description: data.description
});
},

View File

@@ -1,14 +1,5 @@
import { getOrgMembers, putUpdateOrgMembers } from '@/web/support/user/team/org/api';
import {
Box,
Button,
Checkbox,
Flex,
Grid,
HStack,
ModalBody,
ModalFooter
} from '@chakra-ui/react';
import { putUpdateOrgMembers } from '@/web/support/user/team/org/api';
import { Box, Button, Flex, Grid, HStack, ModalBody, ModalFooter } from '@chakra-ui/react';
import type { GroupMemberRole } from '@fastgpt/global/support/permission/memberGroup/constant';
import Avatar from '@fastgpt/web/components/common/Avatar';
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 { useRequest2 } from '@fastgpt/web/hooks/useRequest';
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 { 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 MemberItemCard from '@/components/support/permission/MemberManager/MemberItemCard';
export type GroupFormType = {
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({
currentOrg,
refetchOrgs,
@@ -56,17 +37,24 @@ function OrgMemberManageModal({
ScrollData: MemberScrollData,
isLoading: isLoadingMembers
} = useScrollPagination(getTeamMembers, {
pageSize: 20
pageSize: 20,
params: {
withOrgs: true,
withPermission: false,
status: 'active'
}
});
const {
data: orgMembers,
ScrollData: OrgMemberScrollData,
isLoading: isLoadingOrgMembers
} = useScrollPagination(getOrgMembers, {
pageSize: 20,
} = useScrollPagination(getTeamMembers, {
pageSize: 100000,
params: {
orgPath: getOrgChildrenPath(currentOrg)
orgId: currentOrg._id,
withOrgs: false,
withPermission: false
}
});
@@ -83,11 +71,6 @@ function OrgMemberManageModal({
}, [orgMembers]);
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(
() => {
@@ -165,35 +148,20 @@ function OrgMemberManageModal({
}}
/>
<MemberScrollData mt={3} flexGrow="1" overflow={'auto'} isLoading={isLoadingMembers}>
{filterMembers.map((member) => {
{allMembers.map((member) => {
return (
<HStack
py="2"
px={3}
borderRadius={'md'}
alignItems="center"
<MemberItemCard
avatar={member.avatar}
key={member.tmbId}
cursor={'pointer'}
_hover={{
bg: 'myGray.50',
...(!isSelected(member.tmbId) ? { svg: { color: 'myGray.50' } } : {})
}}
_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>
name={member.memberName}
onChange={() => handleToggleSelect(member.tmbId)}
isChecked={!!isSelected(member.tmbId)}
orgs={member.orgs}
/>
);
})}
</MemberScrollData>
</Flex>
{/* <Flex mt={3} flexDirection="column" flexGrow="1" overflow={'auto'} maxH={'100%'}> */}
<Flex flexDirection="column" p="4" overflowY="auto" overflowX="hidden">
<OrgMemberScrollData
mt={3}

View File

@@ -20,8 +20,6 @@ function OrgMoveModal({
}) {
const { t } = useTranslation();
const [selectedOrg, setSelectedOrg] = useState<OrgListItemType>();
const { userInfo } = useUserStore();
const team = userInfo?.team!;
const { runAsync: onMoveOrg, loading } = useRequest2(putMoveOrg, {
onSuccess: () => {
@@ -39,7 +37,7 @@ function OrgMoveModal({
iconColor="primary.600"
>
<ModalBody>
<OrgTree selectedOrg={selectedOrg} setSelectedOrg={setSelectedOrg} />
<OrgTree selectedOrg={selectedOrg} setSelectedOrg={setSelectedOrg} movingOrg={movingOrg} />
</ModalBody>
<ModalFooter>
<Button

View File

@@ -14,17 +14,19 @@ function OrgTreeNode({
org,
selectedOrg,
setSelectedOrg,
index = 0
index = 0,
movingOrg
}: {
org: OrgListItemType;
selectedOrg?: OrgListItemType;
setSelectedOrg: (org?: OrgListItemType) => void;
index?: number;
movingOrg: OrgListItemType;
}) {
const [isExpanded, toggleIsExpanded] = useToggle(index === 0);
const [canBeExpanded, setCanBeExpanded] = useState(true);
const { data: orgs = [], runAsync: getOrgs } = useRequest2(() =>
getOrgList({ orgPath: getOrgChildrenPath(org) })
getOrgList({ orgId: org._id, withPermission: false })
);
const onClickExpand = async () => {
const data = await getOrgs();
@@ -34,6 +36,9 @@ function OrgTreeNode({
toggleIsExpanded.toggle();
};
if (org._id === movingOrg._id) {
return <></>;
}
return (
<Box userSelect={'none'}>
<HStack
@@ -78,6 +83,7 @@ function OrgTreeNode({
orgs.map((child) => (
<Box key={child._id} mt={0.5}>
<OrgTreeNode
movingOrg={movingOrg}
org={child}
index={index + 1}
selectedOrg={selectedOrg}
@@ -91,10 +97,12 @@ function OrgTreeNode({
function OrgTree({
selectedOrg,
setSelectedOrg
setSelectedOrg,
movingOrg
}: {
selectedOrg?: OrgListItemType;
setSelectedOrg: (org?: OrgListItemType) => void;
movingOrg: OrgListItemType;
}) {
const { userInfo } = useUserStore();
const root: OrgListItemType = {
@@ -107,6 +115,7 @@ function OrgTree({
return (
<OrgTreeNode
movingOrg={movingOrg}
key={'root'}
org={root}
selectedOrg={selectedOrg}

View File

@@ -78,9 +78,6 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
const [editOrg, setEditOrg] = useState<OrgFormType>();
const [manageMemberOrg, setManageMemberOrg] = useState<OrgListItemType>();
const [movingOrg, setMovingOrg] = useState<OrgListItemType>();
const [searchOrg, setSearchOrg] = useState('');
const {
currentOrg,
orgs,
@@ -91,7 +88,9 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
MemberScrollData,
onPathClick,
refresh,
updateCurrentOrg
updateCurrentOrg,
setSearchKey,
searchKey
} = useOrg();
// Delete org
@@ -123,12 +122,6 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
onSuccess: refresh
});
const searchedOrgs = useMemo(() => {
if (!searchOrg) return [];
return orgs.filter((org) => org.name.includes(searchOrg));
}, [orgs, searchOrg]);
return (
<>
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
@@ -136,8 +129,8 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
<Box w="200px">
<SearchInput
placeholder={t('account_team:search_org')}
value={searchOrg}
onChange={(e) => setSearchOrg(e.target.value)}
value={searchKey}
onChange={(e) => setSearchKey(e.target.value)}
/>
</Box>
</Flex>
@@ -166,102 +159,55 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
</Tr>
</Thead>
<Tbody>
{searchedOrgs.map((org) => (
<Tr key={org._id} overflow={'unset'} onClick={() => onClickOrg(org)}>
<Td>
<HStack cursor={'pointer'} onClick={() => onClickOrg(org)}>
<MemberTag name={org.name} avatar={org.avatar!} />
<Tag size="sm">{org.total}</Tag>
<MyIcon
name="core/chat/chevronRight"
w={'1rem'}
h={'1rem'}
color={'myGray.500'}
/>
</HStack>
</Td>
{isTeamAdmin && !isSyncMember && (
<Td w={'6rem'}>
<MyMenu
trigger="hover"
Button={<IconButton name="more" />}
menuList={[
{
children: [
{
icon: 'edit',
label: t('account_team:edit_info'),
onClick: () => setEditOrg(org)
},
{
icon: 'common/file/move',
label: t('common:Move'),
onClick: () => setMovingOrg(org)
},
{
icon: 'delete',
label: t('account_team:delete'),
type: 'danger',
onClick: () => deleteOrgHandler(org._id)
}
]
}
]}
/>
{orgs
.filter((org) => org.path !== '')
.map((org) => (
<Tr key={org._id} overflow={'unset'}>
<Td>
<HStack cursor={'pointer'} onClick={() => onClickOrg(org)}>
<MemberTag name={org.name} avatar={org.avatar} />
<Tag size="sm">{org.total}</Tag>
<MyIcon
name="core/chat/chevronRight"
w={'1rem'}
h={'1rem'}
color={'myGray.500'}
/>
</HStack>
</Td>
)}
</Tr>
))}
{!searchOrg &&
orgs
.filter((org) => org.path !== '')
.map((org) => (
<Tr key={org._id} overflow={'unset'}>
<Td>
<HStack cursor={'pointer'} onClick={() => onClickOrg(org)}>
<MemberTag name={org.name} avatar={org.avatar} />
<Tag size="sm">{org.total}</Tag>
<MyIcon
name="core/chat/chevronRight"
w={'1rem'}
h={'1rem'}
color={'myGray.500'}
/>
</HStack>
{isTeamAdmin && !isSyncMember && (
<Td w={'6rem'}>
<MyMenu
trigger="hover"
Button={<IconButton name="more" />}
menuList={[
{
children: [
{
icon: 'edit',
label: t('account_team:edit_info'),
onClick: () => setEditOrg(org)
},
{
icon: 'common/file/move',
label: t('common:Move'),
onClick: () => setMovingOrg(org)
},
{
icon: 'delete',
label: t('account_team:delete'),
type: 'danger',
onClick: () => deleteOrgHandler(org._id)
}
]
}
]}
/>
</Td>
{isTeamAdmin && !isSyncMember && (
<Td w={'6rem'}>
<MyMenu
trigger="hover"
Button={<IconButton name="more" />}
menuList={[
{
children: [
{
icon: 'edit',
label: t('account_team:edit_info'),
onClick: () => setEditOrg(org)
},
{
icon: 'common/file/move',
label: t('common:Move'),
onClick: () => setMovingOrg(org)
},
{
icon: 'delete',
label: t('account_team:delete'),
type: 'danger',
onClick: () => deleteOrgHandler(org._id)
}
]
}
]}
/>
</Td>
)}
</Tr>
))}
{!searchOrg &&
)}
</Tr>
))}
{!searchKey &&
members.map((member) => {
return (
<Tr key={member.tmbId}>
@@ -403,6 +349,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
onClose={() => setEditOrg(undefined)}
onSuccess={refresh}
updateCurrentOrg={updateCurrentOrg}
parentId={currentOrg._id}
/>
)}
{!!movingOrg && (

View File

@@ -20,20 +20,17 @@ const EditInfoModal = dynamic(() => import('./EditInfoModal'));
type TeamModalContextType = {
myTeams: TeamTmbItemType[];
members: TeamMemberItemType[];
isLoading: boolean;
onSwitchTeam: (teamId: string) => void;
setEditTeamData: React.Dispatch<React.SetStateAction<EditTeamFormDataType | undefined>>;
refetchMembers: () => void;
refetchTeamSize: () => void;
refetchTeams: () => void;
teamSize: number;
MemberScrollData: ReturnType<typeof useScrollPagination>['ScrollData'];
};
export const TeamContext = createContext<TeamModalContextType>({
myTeams: [],
members: [],
isLoading: false,
onSwitchTeam: function (_teamId: string): void {
throw new Error('Function not implemented.');
@@ -44,11 +41,10 @@ export const TeamContext = createContext<TeamModalContextType>({
refetchTeams: function (): void {
throw new Error('Function not implemented.');
},
refetchMembers: function (): void {
refetchTeamSize: function (): void {
throw new Error('Function not implemented.');
},
teamSize: 0,
MemberScrollData: () => <></>
teamSize: 0
});
export const TeamModalContextProvider = ({ children }: { children: ReactNode }) => {
@@ -67,32 +63,11 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
refreshDeps: [userInfo?._id]
});
const { data: teamMemberCountData, refresh: refetchTeamMemberCount } = useRequest2(
getTeamMemberCount,
{
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 { data: teamMemberCountData, refresh: refetchTeamSize } = useRequest2(getTeamMemberCount, {
manual: false,
refreshDeps: [userInfo?.team?.teamId]
});
const refetchMembers = useCallback(() => {
refetchTeamMemberCount();
refetchMemberList();
}, [refetchTeamMemberCount, refetchMemberList]);
const { runAsync: onSwitchTeam, loading: isSwitchingTeam } = useRequest2(
async (teamId: string) => {
await putSwitchTeam(teamId);
@@ -106,7 +81,7 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
}
);
const isLoading = isLoadingTeams || isSwitchingTeam || loadingMembers;
const isLoading = isLoadingTeams || isSwitchingTeam;
const contextValue = {
myTeams,
@@ -116,10 +91,8 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
// create | update team
setEditTeamData,
members,
refetchMembers,
teamSize: teamMemberCountData?.count || 0,
MemberScrollData
refetchTeamSize
};
return (

View File

@@ -34,8 +34,16 @@ export const putSwitchTeam = (teamId: string) =>
PUT<string>(`/proApi/support/user/team/switch`, { teamId });
/* --------------- team member ---------------- */
export const getTeamMembers = (props: PaginationProps<{ withLeaved?: boolean }>) =>
GET<PaginationResponse<TeamMemberItemType>>(`/proApi/support/user/team/member/list`, props);
export const getTeamMembers = (
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 = () =>
GET<{ count: number }>(`/proApi/support/user/team/member/count`);

View File

@@ -10,8 +10,11 @@ import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/t
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
export const getOrgList = (params: { orgPath: string; getPermission?: boolean }) =>
GET<OrgListItemType[]>(`/proApi/support/user/team/org/list`, params);
export const getOrgList = (params: {
orgId: string;
withPermission?: boolean;
searchKey?: string;
}) => POST<OrgListItemType[]>(`/proApi/support/user/team/org/list`, params);
export const postCreateOrg = (data: postCreateOrgData) =>
POST('/proApi/support/user/team/org/create', data);

View File

@@ -1,14 +1,17 @@
import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant';
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 { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { getOrgList, getOrgMembers } from '../api';
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 [searchKey, setSearchKey] = useState('');
const { userInfo } = useUserStore();
@@ -34,10 +37,18 @@ function useOrg({ getPermission = true }: { getPermission?: boolean } = {}) {
data: orgs = [],
loading: isLoadingOrgs,
refresh: refetchOrgs
} = useRequest2(() => getOrgList({ orgPath: path, getPermission }), {
manual: false,
refreshDeps: [userInfo?.team?.teamId, path]
});
} = useRequest2(
() => getOrgList({ orgId: currentOrg._id, withPermission: withPermission, searchKey }),
{
manual: false,
refreshDeps: [userInfo?.team?.teamId, path, currentOrg._id]
}
);
const search = _.debounce(() => {
if (!searchKey) return;
refetchOrgs();
}, 200);
useEffect(() => search, [searchKey]);
const paths = useMemo(() => {
if (!currentOrg) return [];
@@ -53,16 +64,20 @@ function useOrg({ getPermission = true }: { getPermission?: boolean } = {}) {
const onClickOrg = (org: OrgListItemType) => {
setOrgStack([...orgStack, org]);
setSearchKey('');
};
const {
data: members = [],
ScrollData: MemberScrollData,
refreshList: refetchMembers
} = useScrollPagination(getOrgMembers, {
} = useScrollPagination(getTeamMembers, {
pageSize: 20,
params: {
orgPath: path
orgId: currentOrg._id,
withOrgs: false,
withPermission: true,
status: 'active'
},
refreshDeps: [path]
});
@@ -70,6 +85,7 @@ function useOrg({ getPermission = true }: { getPermission?: boolean } = {}) {
const onPathClick = (path: string) => {
const pathIds = path.split('/');
setOrgStack(orgStack.filter((org) => pathIds.includes(org.pathId)));
setSearchKey('');
};
const refresh = () => {
@@ -101,7 +117,9 @@ function useOrg({ getPermission = true }: { getPermission?: boolean } = {}) {
MemberScrollData,
onPathClick,
refresh,
updateCurrentOrg
updateCurrentOrg,
searchKey,
setSearchKey
};
}