mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-27 00:17:31 +00:00
pref: member/group/org (#4316)
* feat: change group owner api * pref: member/org/group * fix: member modal select clb * fix: search member when change owner
This commit is contained in:
@@ -1,12 +1,13 @@
|
|||||||
|
// orgId, pathid, path === null ===> root org
|
||||||
export type postCreateOrgData = {
|
export type postCreateOrgData = {
|
||||||
name: string;
|
name: string;
|
||||||
parentId: string;
|
|
||||||
description?: string;
|
description?: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
|
path?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type putUpdateOrgMembersData = {
|
export type putUpdateOrgMembersData = {
|
||||||
orgId: string;
|
orgId?: string;
|
||||||
members: {
|
members: {
|
||||||
tmbId: string;
|
tmbId: string;
|
||||||
// role: `${OrgMemberRole}`;
|
// role: `${OrgMemberRole}`;
|
||||||
@@ -14,7 +15,7 @@ export type putUpdateOrgMembersData = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type putUpdateOrgData = {
|
export type putUpdateOrgData = {
|
||||||
orgId: string;
|
orgId: string; // can not be undefined because can not uppdate root org
|
||||||
name?: string;
|
name?: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
@@ -22,7 +23,7 @@ export type putUpdateOrgData = {
|
|||||||
|
|
||||||
export type putMoveOrgType = {
|
export type putMoveOrgType = {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
targetOrgId: string;
|
targetOrgId?: string; // '' ===> move to root org
|
||||||
};
|
};
|
||||||
|
|
||||||
// type putChnageOrgOwnerData = {
|
// type putChnageOrgOwnerData = {
|
||||||
|
@@ -3,7 +3,10 @@ import { OrgSchemaType } from './type';
|
|||||||
export const OrgCollectionName = 'team_orgs';
|
export const OrgCollectionName = 'team_orgs';
|
||||||
export const OrgMemberCollectionName = 'team_org_members';
|
export const OrgMemberCollectionName = 'team_org_members';
|
||||||
|
|
||||||
export const getOrgChildrenPath = (org: OrgSchemaType) => `${org.path}/${org.pathId}`;
|
export const getOrgChildrenPath = (org: OrgSchemaType) => {
|
||||||
|
if (org.path === '' && org.pathId === '') return '';
|
||||||
|
return `${org.path ?? ''}/${org.pathId}`;
|
||||||
|
};
|
||||||
|
|
||||||
export enum SyncOrgSourceEnum {
|
export enum SyncOrgSourceEnum {
|
||||||
wecom = 'wecom'
|
wecom = 'wecom'
|
||||||
|
@@ -21,7 +21,7 @@ type OrgMemberSchemaType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type OrgListItemType = OrgSchemaType & {
|
export type OrgListItemType = OrgSchemaType & {
|
||||||
permission: TeamPermission;
|
permission?: TeamPermission;
|
||||||
total: number; // members + children orgs
|
total: number; // members + children orgs
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@ export const authOrgMember = async ({
|
|||||||
orgIds,
|
orgIds,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
orgIds: string | string[];
|
orgIds?: string | string[];
|
||||||
} & AuthModeType): Promise<AuthResponseType> => {
|
} & AuthModeType): Promise<AuthResponseType> => {
|
||||||
const result = await authUserPer({
|
const result = await authUserPer({
|
||||||
...props,
|
...props,
|
||||||
|
@@ -19,7 +19,7 @@ function AvatarGroup({
|
|||||||
avatars: string[];
|
avatars: string[];
|
||||||
total?: number;
|
total?: number;
|
||||||
}) {
|
}) {
|
||||||
const remain = total ?? avatars.length - max;
|
const remain = (total ?? avatars.length) - max;
|
||||||
return (
|
return (
|
||||||
<Flex position="relative">
|
<Flex position="relative">
|
||||||
{avatars.slice(0, max).map((avatar, index) => (
|
{avatars.slice(0, max).map((avatar, index) => (
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { GetSearchUserGroupOrg } from '@/web/support/user/api';
|
||||||
import { getTeamMembers } from '@/web/support/user/team/api';
|
import { getTeamMembers } from '@/web/support/user/team/api';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
@@ -38,16 +39,29 @@ export function ChangeOwnerModal({
|
|||||||
pageSize: 15
|
pageSize: 15
|
||||||
});
|
});
|
||||||
|
|
||||||
const memberList = teamMembers.filter((item) => {
|
const { data: searchedData } = useRequest2(
|
||||||
return item.memberName.includes(inputValue);
|
async () => {
|
||||||
});
|
if (!inputValue) return;
|
||||||
|
return GetSearchUserGroupOrg(inputValue);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
manual: false,
|
||||||
|
refreshDeps: [inputValue],
|
||||||
|
throttleWait: 500,
|
||||||
|
debounceWait: 200
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const memberList = searchedData ? searchedData.members : teamMembers;
|
||||||
const {
|
const {
|
||||||
isOpen: isOpenMemberListMenu,
|
isOpen: isOpenMemberListMenu,
|
||||||
onClose: onCloseMemberListMenu,
|
onClose: onCloseMemberListMenu,
|
||||||
onOpen: onOpenMemberListMenu
|
onOpen: onOpenMemberListMenu
|
||||||
} = useDisclosure();
|
} = useDisclosure();
|
||||||
const [selectedMember, setSelectedMember] = useState<TeamMemberItemType | null>(null);
|
const [selectedMember, setSelectedMember] = useState<Omit<
|
||||||
|
TeamMemberItemType,
|
||||||
|
'permission' | 'teamId'
|
||||||
|
> | null>(null);
|
||||||
|
|
||||||
const { runAsync, loading } = useRequest2(onChangeOwner, {
|
const { runAsync, loading } = useRequest2(onChangeOwner, {
|
||||||
onSuccess: onClose,
|
onSuccess: onClose,
|
||||||
|
@@ -30,7 +30,7 @@ import {
|
|||||||
import Path from '@/components/common/folder/Path';
|
import Path from '@/components/common/folder/Path';
|
||||||
import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant';
|
import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant';
|
||||||
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
|
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
|
||||||
import { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
import { OrgListItemType, OrgType } from '@fastgpt/global/support/user/team/org/type';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import { CollaboratorContext } from './context';
|
import { CollaboratorContext } from './context';
|
||||||
import { getTeamMembers } from '@/web/support/user/team/api';
|
import { getTeamMembers } from '@/web/support/user/team/api';
|
||||||
@@ -39,6 +39,9 @@ import { getOrgList, getOrgMembers } from '@/web/support/user/team/org/api';
|
|||||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||||
import MemberItemCard from './MemberItemCard';
|
import MemberItemCard from './MemberItemCard';
|
||||||
import { GetSearchUserGroupOrg } from '@/web/support/user/api';
|
import { GetSearchUserGroupOrg } from '@/web/support/user/api';
|
||||||
|
import useOrg from '@/web/support/user/team/org/hooks/useOrg';
|
||||||
|
import { MemberGroupListType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||||
|
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
|
||||||
|
|
||||||
const HoverBoxStyle = {
|
const HoverBoxStyle = {
|
||||||
bgColor: 'myGray.50',
|
bgColor: 'myGray.50',
|
||||||
@@ -57,48 +60,21 @@ function MemberModal({
|
|||||||
const collaboratorList = useContextSelector(CollaboratorContext, (v) => v.collaboratorList);
|
const collaboratorList = useContextSelector(CollaboratorContext, (v) => v.collaboratorList);
|
||||||
const [searchText, setSearchText] = useState<string>('');
|
const [searchText, setSearchText] = useState<string>('');
|
||||||
const [filterClass, setFilterClass] = useState<'member' | 'org' | 'group'>();
|
const [filterClass, setFilterClass] = useState<'member' | 'org' | 'group'>();
|
||||||
const [path, setPath] = useState('');
|
const {
|
||||||
const [orgStack, setOrgStack] = useState<OrgType[]>([]);
|
paths,
|
||||||
const currentOrg = useMemo(() => orgStack[orgStack.length - 1], [orgStack]);
|
onClickOrg,
|
||||||
|
members: orgMembers,
|
||||||
|
MemberScrollData: OrgMemberScrollData,
|
||||||
|
onPathClick,
|
||||||
|
refresh,
|
||||||
|
updateCurrentOrg,
|
||||||
|
orgs
|
||||||
|
} = useOrg({ getPermission: false });
|
||||||
|
|
||||||
const { data: members, ScrollData: TeamMemberScrollData } = useScrollPagination(getTeamMembers, {
|
const { data: members, ScrollData: TeamMemberScrollData } = useScrollPagination(getTeamMembers, {
|
||||||
pageSize: 15
|
pageSize: 15
|
||||||
});
|
});
|
||||||
|
|
||||||
const [rootOrg, setRootOrg] = useState<OrgType>();
|
|
||||||
const { data: orgMembers = [], ScrollData: OrgMemberScrollData } = useScrollPagination(
|
|
||||||
getOrgMembers,
|
|
||||||
{
|
|
||||||
pageSize: 20,
|
|
||||||
params: {
|
|
||||||
orgId: currentOrg?._id ?? rootOrg?._id
|
|
||||||
},
|
|
||||||
refreshDeps: [currentOrg?._id]
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const onClickOrg = (org: OrgType) => {
|
|
||||||
setOrgStack([...orgStack, org]);
|
|
||||||
setPath(getOrgChildrenPath(org));
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data: orgs = [] } = useRequest2(
|
|
||||||
() => {
|
|
||||||
const splitPath = path.split('/').filter(Boolean);
|
|
||||||
const orgs = orgStack.filter((o) => splitPath.includes(o.pathId));
|
|
||||||
setOrgStack(orgs);
|
|
||||||
return getOrgList(path);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
manual: false,
|
|
||||||
refreshDeps: [path],
|
|
||||||
onSuccess: (data) => {
|
|
||||||
if (!rootOrg) {
|
|
||||||
setRootOrg(data[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: groups = [], loading: loadingGroupsAndOrgs } = useRequest2(
|
const { data: groups = [], loading: loadingGroupsAndOrgs } = useRequest2(
|
||||||
async () => {
|
async () => {
|
||||||
if (!userInfo?.team?.teamId) return [];
|
if (!userInfo?.team?.teamId) return [];
|
||||||
@@ -117,21 +93,9 @@ function MemberModal({
|
|||||||
refreshDeps: [searchText]
|
refreshDeps: [searchText]
|
||||||
});
|
});
|
||||||
|
|
||||||
const paths = useMemo(() => {
|
const [selectedOrgList, setSelectedOrgIdList] = useState<OrgListItemType[]>([]);
|
||||||
return orgStack
|
|
||||||
.map((org) => {
|
|
||||||
if (org?.path === '') return;
|
|
||||||
return {
|
|
||||||
parentId: getOrgChildrenPath(org),
|
|
||||||
parentName: org.name
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter(Boolean) as ParentTreePathItemType[];
|
|
||||||
}, [orgStack]);
|
|
||||||
|
|
||||||
const [selectedOrgIdList, setSelectedOrgIdList] = useState<string[]>([]);
|
const filterOrgs: (OrgListItemType & { count?: number })[] = useMemo(() => {
|
||||||
|
|
||||||
const filterOrgs: (OrgType & { count?: number })[] = useMemo(() => {
|
|
||||||
if (searchText && searchedData) {
|
if (searchText && searchedData) {
|
||||||
const orgids = searchedData.orgs.map((item) => item._id);
|
const orgids = searchedData.orgs.map((item) => item._id);
|
||||||
return orgs.filter((org) => orgids.includes(String(org._id)));
|
return orgs.filter((org) => orgids.includes(String(org._id)));
|
||||||
@@ -144,7 +108,9 @@ function MemberModal({
|
|||||||
}));
|
}));
|
||||||
}, [searchText, orgs, searchedData]);
|
}, [searchText, orgs, searchedData]);
|
||||||
|
|
||||||
const [selectedMemberIdList, setSelectedMembers] = useState<string[]>([]);
|
const [selectedMemberList, setSelectedMemberList] = useState<
|
||||||
|
Omit<TeamMemberItemType, 'permission' | 'teamId'>[]
|
||||||
|
>([]);
|
||||||
const filterMembers = useMemo(() => {
|
const filterMembers = useMemo(() => {
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
return searchedData?.members || [];
|
return searchedData?.members || [];
|
||||||
@@ -152,9 +118,8 @@ function MemberModal({
|
|||||||
|
|
||||||
return members;
|
return members;
|
||||||
}, [searchText, members, searchedData?.members]);
|
}, [searchText, members, searchedData?.members]);
|
||||||
console.log(filterMembers);
|
|
||||||
|
|
||||||
const [selectedGroupIdList, setSelectedGroupIdList] = useState<string[]>([]);
|
const [selectedGroupList, setSelectedGroupList] = useState<MemberGroupListType>([]);
|
||||||
const filterGroups = useMemo(() => {
|
const filterGroups = useMemo(() => {
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
return searchedData?.groups.map((item) => ({
|
return searchedData?.groups.map((item) => ({
|
||||||
@@ -186,9 +151,9 @@ function MemberModal({
|
|||||||
const { runAsync: onConfirm, loading: isUpdating } = useRequest2(
|
const { runAsync: onConfirm, loading: isUpdating } = useRequest2(
|
||||||
() =>
|
() =>
|
||||||
onUpdateCollaborators({
|
onUpdateCollaborators({
|
||||||
members: selectedMemberIdList,
|
members: selectedMemberList.map((item) => item.tmbId),
|
||||||
groups: selectedGroupIdList,
|
groups: selectedGroupList.map((item) => item._id),
|
||||||
orgs: selectedOrgIdList,
|
orgs: selectedOrgList.map((item) => item._id),
|
||||||
permission: selectedPermission!
|
permission: selectedPermission!
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
@@ -206,42 +171,31 @@ function MemberModal({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const selectedList = useMemo(() => {
|
const selectedList = useMemo(() => {
|
||||||
const selectedOrgs = orgs.filter((org) => selectedOrgIdList.includes(org._id));
|
|
||||||
const selectedGroups = groups.filter((group) => selectedGroupIdList.includes(group._id));
|
|
||||||
const selectedMembers = members.filter((member) => selectedMemberIdList.includes(member.tmbId));
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...selectedOrgs.map((item) => ({
|
...selectedOrgList.map((item) => ({
|
||||||
id: `org-${item._id}`,
|
id: `org-${item._id}`,
|
||||||
avatar: item.avatar,
|
avatar: item.avatar,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
onDelete: () => setSelectedOrgIdList(selectedOrgIdList.filter((v) => v !== item._id)),
|
onDelete: () => setSelectedOrgIdList(selectedOrgList.filter((v) => v._id !== item._id)),
|
||||||
orgs: undefined
|
orgs: undefined
|
||||||
})),
|
})),
|
||||||
...selectedGroups.map((item) => ({
|
...selectedGroupList.map((item) => ({
|
||||||
id: `group-${item._id}`,
|
id: `group-${item._id}`,
|
||||||
avatar: item.avatar,
|
avatar: item.avatar,
|
||||||
name: item.name === DefaultGroupName ? userInfo?.team.teamName : item.name,
|
name: item.name === DefaultGroupName ? userInfo?.team.teamName : item.name,
|
||||||
onDelete: () => setSelectedGroupIdList(selectedGroupIdList.filter((v) => v !== item._id)),
|
onDelete: () => setSelectedGroupList(selectedGroupList.filter((v) => v._id !== item._id)),
|
||||||
orgs: undefined
|
orgs: undefined
|
||||||
})),
|
})),
|
||||||
...selectedMembers.map((item) => ({
|
...selectedMemberList.map((item) => ({
|
||||||
id: `member-${item.tmbId}`,
|
id: `member-${item.tmbId}`,
|
||||||
avatar: item.avatar,
|
avatar: item.avatar,
|
||||||
name: item.memberName,
|
name: item.memberName,
|
||||||
onDelete: () => setSelectedMembers(selectedMemberIdList.filter((v) => v !== item.tmbId)),
|
onDelete: () =>
|
||||||
|
setSelectedMemberList(selectedMemberList.filter((v) => v.tmbId !== item.tmbId)),
|
||||||
orgs: item.orgs
|
orgs: item.orgs
|
||||||
}))
|
}))
|
||||||
];
|
];
|
||||||
}, [
|
}, [selectedOrgList, selectedGroupList, selectedMemberList, userInfo?.team.teamName]);
|
||||||
orgs,
|
|
||||||
groups,
|
|
||||||
members,
|
|
||||||
selectedOrgIdList,
|
|
||||||
selectedGroupIdList,
|
|
||||||
selectedMemberIdList,
|
|
||||||
userInfo?.team.teamName
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MyModal
|
<MyModal
|
||||||
@@ -324,36 +278,31 @@ function MemberModal({
|
|||||||
onClick={(parentId) => {
|
onClick={(parentId) => {
|
||||||
if (parentId === '') {
|
if (parentId === '') {
|
||||||
setFilterClass(undefined);
|
setFilterClass(undefined);
|
||||||
setPath('');
|
onPathClick('');
|
||||||
} else if (
|
} else if (
|
||||||
parentId === 'member' ||
|
parentId === 'member' ||
|
||||||
parentId === 'org' ||
|
parentId === 'org' ||
|
||||||
parentId === 'group'
|
parentId === 'group'
|
||||||
) {
|
) {
|
||||||
setFilterClass(parentId);
|
setFilterClass(parentId);
|
||||||
setPath('');
|
onPathClick('');
|
||||||
} else {
|
} else {
|
||||||
setPath(parentId);
|
onPathClick(parentId);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
rootName={t('common:common.Team')}
|
rootName={t('common:common.Team')}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{(filterClass === 'member' || (searchText && filterMembers.length > 0)) && (
|
{(filterClass === 'member' || (searchText && filterMembers.length > 0)) &&
|
||||||
<TeamMemberScrollData
|
(() => {
|
||||||
flexDirection={'column'}
|
const members = filterMembers?.map((member) => {
|
||||||
gap={1}
|
|
||||||
userSelect={'none'}
|
|
||||||
height={'fit-content'}
|
|
||||||
>
|
|
||||||
{filterMembers?.map((member) => {
|
|
||||||
const onChange = () => {
|
const onChange = () => {
|
||||||
setSelectedMembers((state) => {
|
setSelectedMemberList((state) => {
|
||||||
if (state.includes(member.tmbId)) {
|
if (state.find((v) => v.tmbId === member.tmbId)) {
|
||||||
return state.filter((v) => v !== member.tmbId);
|
return state.filter((v) => v.tmbId !== member.tmbId);
|
||||||
}
|
}
|
||||||
return [...state, member.tmbId];
|
return [...state, member];
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId);
|
const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId);
|
||||||
@@ -364,23 +313,33 @@ function MemberModal({
|
|||||||
name={member.memberName}
|
name={member.memberName}
|
||||||
permission={collaborator?.permission.value}
|
permission={collaborator?.permission.value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
isChecked={selectedMemberIdList.includes(member.tmbId)}
|
isChecked={!!selectedMemberList.find((v) => v.tmbId === member.tmbId)}
|
||||||
orgs={member.orgs}
|
orgs={member.orgs}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
});
|
||||||
</TeamMemberScrollData>
|
return searchText ? (
|
||||||
)}
|
members
|
||||||
|
) : (
|
||||||
|
<TeamMemberScrollData
|
||||||
|
flexDirection={'column'}
|
||||||
|
gap={1}
|
||||||
|
userSelect={'none'}
|
||||||
|
height={'fit-content'}
|
||||||
|
>
|
||||||
|
{members}
|
||||||
|
</TeamMemberScrollData>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
{(filterClass === 'org' || searchText) &&
|
{(filterClass === 'org' || searchText) &&
|
||||||
(() => {
|
(() => {
|
||||||
const orgs = filterOrgs?.map((org) => {
|
const orgs = filterOrgs?.map((org) => {
|
||||||
const onChange = () => {
|
const onChange = () => {
|
||||||
setSelectedOrgIdList((state) => {
|
setSelectedOrgIdList((state) => {
|
||||||
if (state.includes(org._id)) {
|
if (state.find((v) => v._id === org._id)) {
|
||||||
return state.filter((v) => v !== org._id);
|
return state.filter((v) => v._id !== org._id);
|
||||||
}
|
}
|
||||||
return [...state, org._id];
|
return [...state, org];
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const collaborator = collaboratorList?.find((v) => v.orgId === org._id);
|
const collaborator = collaboratorList?.find((v) => v.orgId === org._id);
|
||||||
@@ -396,7 +355,7 @@ function MemberModal({
|
|||||||
onClick={onChange}
|
onClick={onChange}
|
||||||
>
|
>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
isChecked={selectedOrgIdList.includes(org._id)}
|
isChecked={!!selectedOrgList.find((v) => v._id === org._id)}
|
||||||
pointerEvents="none"
|
pointerEvents="none"
|
||||||
/>
|
/>
|
||||||
<MyAvatar src={org.avatar} w="1.5rem" borderRadius={'50%'} />
|
<MyAvatar src={org.avatar} w="1.5rem" borderRadius={'50%'} />
|
||||||
@@ -442,14 +401,14 @@ function MemberModal({
|
|||||||
key={member.tmbId}
|
key={member.tmbId}
|
||||||
name={member.memberName}
|
name={member.memberName}
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
setSelectedMembers((state) => {
|
setSelectedMemberList((state) => {
|
||||||
if (state.includes(member.tmbId)) {
|
if (state.find((v) => v.tmbId === member.tmbId)) {
|
||||||
return state.filter((v) => v !== member.tmbId);
|
return state.filter((v) => v.tmbId !== member.tmbId);
|
||||||
}
|
}
|
||||||
return [...state, member.tmbId];
|
return [...state, member];
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
isChecked={selectedMemberIdList.includes(member.tmbId)}
|
isChecked={!!selectedMemberList.find((v) => v.tmbId === member.tmbId)}
|
||||||
orgs={member.orgs}
|
orgs={member.orgs}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -459,11 +418,11 @@ function MemberModal({
|
|||||||
})()}
|
})()}
|
||||||
{filterGroups?.map((group) => {
|
{filterGroups?.map((group) => {
|
||||||
const onChange = () => {
|
const onChange = () => {
|
||||||
setSelectedGroupIdList((state) => {
|
setSelectedGroupList((state) => {
|
||||||
if (state.includes(group._id)) {
|
if (state.find((v) => v._id === group._id)) {
|
||||||
return state.filter((v) => v !== group._id);
|
return state.filter((v) => v._id !== group._id);
|
||||||
}
|
}
|
||||||
return [...state, group._id];
|
return [...state, group];
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const collaborator = collaboratorList?.find((v) => v.groupId === group._id);
|
const collaborator = collaboratorList?.find((v) => v.groupId === group._id);
|
||||||
@@ -476,7 +435,7 @@ function MemberModal({
|
|||||||
}
|
}
|
||||||
permission={collaborator?.permission.value}
|
permission={collaborator?.permission.value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
isChecked={selectedGroupIdList.includes(group._id)}
|
isChecked={!!selectedGroupList.find((v) => v._id === group._id)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -486,7 +445,7 @@ function MemberModal({
|
|||||||
<Flex h={'100%'} p="4" flexDirection="column">
|
<Flex h={'100%'} p="4" flexDirection="column">
|
||||||
<Box>
|
<Box>
|
||||||
{`${t('user:has_chosen')}: `}
|
{`${t('user:has_chosen')}: `}
|
||||||
{selectedMemberIdList.length + selectedGroupIdList.length + selectedOrgIdList.length}
|
{selectedMemberList.length + selectedGroupList.length + selectedOrgList.length}
|
||||||
</Box>
|
</Box>
|
||||||
<Flex flexDirection="column" mt="2" gap={1} overflow={'auto'} flex={'1 0 0'} h={0}>
|
<Flex flexDirection="column" mt="2" gap={1} overflow={'auto'} flex={'1 0 0'} h={0}>
|
||||||
{selectedList.map((item) => {
|
{selectedList.map((item) => {
|
||||||
|
@@ -12,14 +12,25 @@ import { useContextSelector } from 'use-context-selector';
|
|||||||
import { TeamContext } from '../context';
|
import { TeamContext } from '../context';
|
||||||
import { postCreateGroup, putUpdateGroup } from '@/web/support/user/team/group/api';
|
import { postCreateGroup, putUpdateGroup } from '@/web/support/user/team/group/api';
|
||||||
import { DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants';
|
import { DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants';
|
||||||
|
import { MemberGroupListType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||||
|
|
||||||
export type GroupFormType = {
|
export type GroupFormType = {
|
||||||
avatar: string;
|
avatar: string;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function GroupInfoModal({ onClose, editGroupId }: { onClose: () => void; editGroupId?: string }) {
|
function GroupInfoModal({
|
||||||
const { refetchGroups, groups, refetchMembers } = useContextSelector(TeamContext, (v) => v);
|
onClose,
|
||||||
|
editGroupId,
|
||||||
|
groups,
|
||||||
|
refetchGroups
|
||||||
|
}: {
|
||||||
|
onClose: () => void;
|
||||||
|
editGroupId?: string;
|
||||||
|
groups: MemberGroupListType;
|
||||||
|
refetchGroups: () => void;
|
||||||
|
}) {
|
||||||
|
const { refetchMembers } = useContextSelector(TeamContext, (v) => v);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const {
|
const {
|
||||||
File: AvatarSelect,
|
File: AvatarSelect,
|
||||||
|
@@ -24,7 +24,10 @@ 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 { GroupMemberItemType } from '@fastgpt/global/support/permission/memberGroup/type';
|
import {
|
||||||
|
GroupMemberItemType,
|
||||||
|
MemberGroupListType
|
||||||
|
} from '@fastgpt/global/support/permission/memberGroup/type';
|
||||||
import { useMount } from 'ahooks';
|
import { useMount } from 'ahooks';
|
||||||
|
|
||||||
export type GroupFormType = {
|
export type GroupFormType = {
|
||||||
@@ -37,13 +40,21 @@ export type GroupFormType = {
|
|||||||
// 1. Owner can not be deleted, toast
|
// 1. Owner can not be deleted, toast
|
||||||
// 2. Owner/Admin can manage members
|
// 2. Owner/Admin can manage members
|
||||||
// 3. Owner can add/remove admins
|
// 3. Owner can add/remove admins
|
||||||
function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGroupId?: string }) {
|
function GroupEditModal({
|
||||||
|
onClose,
|
||||||
|
editGroupId,
|
||||||
|
groups,
|
||||||
|
refetchGroups
|
||||||
|
}: {
|
||||||
|
onClose: () => void;
|
||||||
|
editGroupId?: string;
|
||||||
|
groups: MemberGroupListType;
|
||||||
|
refetchGroups: () => void;
|
||||||
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const groups = useContextSelector(TeamContext, (v) => v.groups);
|
|
||||||
const refetchGroups = useContextSelector(TeamContext, (v) => v.refetchGroups);
|
|
||||||
const group = useMemo(() => {
|
const group = useMemo(() => {
|
||||||
return groups.find((item) => item._id === editGroupId);
|
return groups.find((item) => item._id === editGroupId);
|
||||||
}, [editGroupId, groups]);
|
}, [editGroupId, groups]);
|
||||||
|
@@ -18,25 +18,43 @@ import { useTranslation } from 'next-i18next';
|
|||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { TeamContext } from '../context';
|
import { TeamContext } from '../context';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import { MemberGroupListType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||||
|
import { GetSearchUserGroupOrg } from '@/web/support/user/api';
|
||||||
|
import { Omit } from '@fastgpt/web/components/common/DndDrag';
|
||||||
|
|
||||||
export type ChangeOwnerModalProps = {
|
export type ChangeOwnerModalProps = {
|
||||||
groupId: string;
|
groupId: string;
|
||||||
|
groups: MemberGroupListType;
|
||||||
|
refetchGroups: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ChangeOwnerModal({
|
export function ChangeOwnerModal({
|
||||||
onClose,
|
onClose,
|
||||||
groupId
|
groupId,
|
||||||
|
groups,
|
||||||
|
refetchGroups
|
||||||
}: ChangeOwnerModalProps & { onClose: () => void }) {
|
}: ChangeOwnerModalProps & { onClose: () => void }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [inputValue, setInputValue] = React.useState('');
|
const [inputValue, setInputValue] = React.useState('');
|
||||||
const { members: allMembers, groups, refetchGroups } = useContextSelector(TeamContext, (v) => v);
|
const { data: searchedData } = useRequest2(
|
||||||
|
async () => {
|
||||||
|
if (!inputValue) return;
|
||||||
|
return GetSearchUserGroupOrg(inputValue);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
manual: false,
|
||||||
|
refreshDeps: [inputValue],
|
||||||
|
throttleWait: 500,
|
||||||
|
debounceWait: 200
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { members: allMembers } = useContextSelector(TeamContext, (v) => v);
|
||||||
const group = useMemo(() => {
|
const group = useMemo(() => {
|
||||||
return groups.find((item) => item._id === groupId);
|
return groups.find((item) => item._id === groupId);
|
||||||
}, [groupId, groups]);
|
}, [groupId, groups]);
|
||||||
|
|
||||||
const memberList = allMembers.filter((item) => {
|
const memberList = searchedData ? searchedData.members : allMembers;
|
||||||
return item.memberName.toLowerCase().includes(inputValue.toLowerCase());
|
|
||||||
});
|
|
||||||
|
|
||||||
const [keepAdmin, setKeepAdmin] = useState(true);
|
const [keepAdmin, setKeepAdmin] = useState(true);
|
||||||
|
|
||||||
@@ -46,7 +64,10 @@ export function ChangeOwnerModal({
|
|||||||
onOpen: onOpenMemberListMenu
|
onOpen: onOpenMemberListMenu
|
||||||
} = useDisclosure();
|
} = useDisclosure();
|
||||||
|
|
||||||
const [selectedMember, setSelectedMember] = useState<TeamMemberItemType | null>(null);
|
const [selectedMember, setSelectedMember] = useState<Omit<
|
||||||
|
TeamMemberItemType,
|
||||||
|
'permission' | 'teamId'
|
||||||
|
> | null>(null);
|
||||||
|
|
||||||
const { runAsync, loading } = useRequest2(
|
const { runAsync, loading } = useRequest2(
|
||||||
(tmbId: string) => putGroupChangeOwner(groupId, tmbId),
|
(tmbId: string) => putGroupChangeOwner(groupId, tmbId),
|
||||||
|
@@ -22,7 +22,7 @@ import MyMenu, { MenuItemType } from '@fastgpt/web/components/common/MyMenu';
|
|||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { deleteGroup, getGroupMembers } from '@/web/support/user/team/group/api';
|
import { deleteGroup, getGroupList, getGroupMembers } from '@/web/support/user/team/group/api';
|
||||||
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||||
import MemberTag from '../../../../components/support/user/team/Info/MemberTag';
|
import MemberTag from '../../../../components/support/user/team/Info/MemberTag';
|
||||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||||
@@ -39,9 +39,19 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
|
|
||||||
const { groups, refetchGroups, members, teamSize } = useContextSelector(TeamContext, (v) => v);
|
const { members, teamSize } = useContextSelector(TeamContext, (v) => v);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: groups = [],
|
||||||
|
loading: isLoadingGroups,
|
||||||
|
refresh: refetchGroups
|
||||||
|
} = useRequest2(getGroupList, {
|
||||||
|
manual: false,
|
||||||
|
refreshDeps: [userInfo?.team?.teamId]
|
||||||
|
});
|
||||||
|
|
||||||
const [editGroup, setEditGroup] = useState<MemberGroupType>();
|
const [editGroup, setEditGroup] = useState<MemberGroupType>();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOpen: isOpenGroupInfo,
|
isOpen: isOpenGroupInfo,
|
||||||
onOpen: onOpenGroupInfo,
|
onOpen: onOpenGroupInfo,
|
||||||
@@ -106,7 +116,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<MyBox flex={'1 0 0'} overflow={'auto'}>
|
<MyBox flex={'1 0 0'} overflow={'auto'} isLoading={isLoadingGroups}>
|
||||||
<TableContainer overflow={'unset'} fontSize={'sm'}>
|
<TableContainer overflow={'unset'} fontSize={'sm'}>
|
||||||
<Table overflow={'unset'}>
|
<Table overflow={'unset'}>
|
||||||
<Thead>
|
<Thead>
|
||||||
@@ -230,10 +240,17 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
|
|
||||||
<ConfirmDeleteGroupModal />
|
<ConfirmDeleteGroupModal />
|
||||||
{isOpenChangeOwner && editGroup && (
|
{isOpenChangeOwner && editGroup && (
|
||||||
<ChangeOwnerModal groupId={editGroup._id} onClose={onCloseChangeOwner} />
|
<ChangeOwnerModal
|
||||||
|
groupId={editGroup._id}
|
||||||
|
onClose={onCloseChangeOwner}
|
||||||
|
groups={groups}
|
||||||
|
refetchGroups={refetchGroups}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{isOpenGroupInfo && (
|
{isOpenGroupInfo && (
|
||||||
<GroupInfoModal
|
<GroupInfoModal
|
||||||
|
groups={groups}
|
||||||
|
refetchGroups={refetchGroups}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
onCloseGroupInfo();
|
onCloseGroupInfo();
|
||||||
setEditGroup(undefined);
|
setEditGroup(undefined);
|
||||||
@@ -243,6 +260,8 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
)}
|
)}
|
||||||
{isOpenManageGroupMember && editGroup && (
|
{isOpenManageGroupMember && editGroup && (
|
||||||
<GroupManageMember
|
<GroupManageMember
|
||||||
|
groups={groups}
|
||||||
|
refetchGroups={refetchGroups}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
onCloseManageGroupMember();
|
onCloseManageGroupMember();
|
||||||
setEditGroup(undefined);
|
setEditGroup(undefined);
|
||||||
|
@@ -44,24 +44,20 @@ import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
|||||||
import { useState } from 'react';
|
import { 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';
|
||||||
|
|
||||||
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'));
|
||||||
|
|
||||||
function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
const { feConfigs } = useSystemStore();
|
const { feConfigs } = useSystemStore();
|
||||||
|
|
||||||
const {
|
const { myTeams, refetchTeams, members, refetchMembers, onSwitchTeam, MemberScrollData } =
|
||||||
refetchGroups,
|
useContextSelector(TeamContext, (v) => v);
|
||||||
myTeams,
|
|
||||||
refetchTeams,
|
|
||||||
members,
|
|
||||||
refetchMembers,
|
|
||||||
onSwitchTeam,
|
|
||||||
MemberScrollData
|
|
||||||
} = useContextSelector(TeamContext, (v) => v);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOpen: isOpenTeamTagsAsync,
|
isOpen: isOpenTeamTagsAsync,
|
||||||
@@ -85,7 +81,10 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
const isSyncMember = feConfigs.register_method?.includes('sync');
|
const isSyncMember = feConfigs.register_method?.includes('sync');
|
||||||
|
|
||||||
const { data: searchMembersData } = useRequest2(
|
const { data: searchMembersData } = useRequest2(
|
||||||
() => GetSearchUserGroupOrg(searchText, { members: true, orgs: false, groups: false }),
|
async () => {
|
||||||
|
if (!searchText) return Promise.resolve();
|
||||||
|
return GetSearchUserGroupOrg(searchText, { members: true, orgs: false, groups: false });
|
||||||
|
},
|
||||||
{
|
{
|
||||||
manual: false,
|
manual: false,
|
||||||
throttleWait: 500,
|
throttleWait: 500,
|
||||||
@@ -137,13 +136,12 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
canEmpty: false,
|
canEmpty: false,
|
||||||
rows: 1
|
rows: 1
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleEditMemberName = (tmbId: string, memberName: string) => {
|
const handleEditMemberName = (tmbId: string, memberName: string) => {
|
||||||
openEditMemberName({
|
openEditMemberName({
|
||||||
defaultVal: memberName,
|
defaultVal: memberName,
|
||||||
onSuccess: (newName: string) => {
|
onSuccess: (newName: string) => {
|
||||||
return putUpdateMemberNameByManager(tmbId, newName).then(() => {
|
return putUpdateMemberNameByManager(tmbId, newName).then(() => {
|
||||||
Promise.all([refetchGroups(), refetchMembers()]);
|
refetchMembers();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
@@ -323,10 +321,27 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
openRemoveMember(
|
openRemoveMember(
|
||||||
() =>
|
() => delRemoveMember(member.tmbId).then(refetchMembers),
|
||||||
delRemoveMember(member.tmbId).then(() =>
|
undefined,
|
||||||
Promise.all([refetchGroups(), refetchMembers()])
|
t('account_team:remove_tip', {
|
||||||
),
|
username: member.memberName
|
||||||
|
})
|
||||||
|
)();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
name={'common/trash'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
w="1rem"
|
||||||
|
p="1"
|
||||||
|
borderRadius="sm"
|
||||||
|
_hover={{
|
||||||
|
color: 'red.600',
|
||||||
|
bgColor: 'myGray.100'
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
openRemoveMember(
|
||||||
|
() => delRemoveMember(member.tmbId).then(refetchMembers),
|
||||||
undefined,
|
undefined,
|
||||||
t('account_team:remove_tip', {
|
t('account_team:remove_tip', {
|
||||||
username: member.memberName
|
username: member.memberName
|
||||||
|
@@ -14,8 +14,7 @@ export type OrgFormType = {
|
|||||||
avatar: string;
|
avatar: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
name: string;
|
name: string;
|
||||||
path: string;
|
path?: string;
|
||||||
parentId?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultOrgForm: OrgFormType = {
|
export const defaultOrgForm: OrgFormType = {
|
||||||
@@ -29,11 +28,13 @@ export const defaultOrgForm: OrgFormType = {
|
|||||||
function OrgInfoModal({
|
function OrgInfoModal({
|
||||||
editOrg,
|
editOrg,
|
||||||
onClose,
|
onClose,
|
||||||
onSuccess
|
onSuccess,
|
||||||
|
updateCurrentOrg
|
||||||
}: {
|
}: {
|
||||||
editOrg: OrgFormType;
|
editOrg: OrgFormType;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSuccess: () => void;
|
onSuccess: () => void;
|
||||||
|
updateCurrentOrg: (data: { name?: string; avatar?: string; description?: string }) => void;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -50,11 +51,10 @@ function OrgInfoModal({
|
|||||||
|
|
||||||
const { run: onCreate, loading: isLoadingCreate } = useRequest2(
|
const { run: onCreate, loading: isLoadingCreate } = useRequest2(
|
||||||
async (data: OrgFormType) => {
|
async (data: OrgFormType) => {
|
||||||
if (!editOrg.parentId) return;
|
|
||||||
return postCreateOrg({
|
return postCreateOrg({
|
||||||
name: data.name,
|
name: data.name,
|
||||||
avatar: data.avatar,
|
avatar: data.avatar,
|
||||||
parentId: editOrg.parentId,
|
path: editOrg.path,
|
||||||
description: data.description
|
description: data.description
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -67,7 +67,7 @@ function OrgInfoModal({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const { run: onUpdate, loading: isLoadingUpdate } = useRequest2(
|
const { runAsync: onUpdate, loading: isLoadingUpdate } = useRequest2(
|
||||||
async (data: OrgFormType) => {
|
async (data: OrgFormType) => {
|
||||||
if (!editOrg._id) return;
|
if (!editOrg._id) return;
|
||||||
return putUpdateOrg({
|
return putUpdateOrg({
|
||||||
@@ -144,7 +144,9 @@ function OrgInfoModal({
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
onClick={handleSubmit((data) => {
|
onClick={handleSubmit((data) => {
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
onUpdate(data);
|
onUpdate(data).then(() => {
|
||||||
|
updateCurrentOrg(data);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
onCreate(data);
|
onCreate(data);
|
||||||
}
|
}
|
||||||
|
@@ -17,12 +17,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 type React from 'react';
|
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { OrgListItemType } from '@fastgpt/global/support/user/team/org/type';
|
||||||
import { TeamContext } from '../context';
|
|
||||||
import { OrgListItemType, OrgType } 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';
|
||||||
|
|
||||||
export type GroupFormType = {
|
export type GroupFormType = {
|
||||||
members: {
|
members: {
|
||||||
@@ -51,20 +50,39 @@ function OrgMemberManageModal({
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { members: allMembers, MemberScrollData } = useContextSelector(TeamContext, (v) => v);
|
|
||||||
const { data: orgMembers, ScrollData: OrgMemberScrollData } = useScrollPagination(getOrgMembers, {
|
const {
|
||||||
|
data: allMembers,
|
||||||
|
ScrollData: MemberScrollData,
|
||||||
|
isLoading: isLoadingMembers
|
||||||
|
} = useScrollPagination(getTeamMembers, {
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
params: {
|
params: {
|
||||||
orgId: currentOrg?._id ?? ''
|
withLeaved: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const [selectedMembers, setSelectedMembers] = useState<string[]>(
|
const {
|
||||||
orgMembers.map((item) => item.tmbId)
|
data: orgMembers,
|
||||||
);
|
ScrollData: OrgMemberScrollData,
|
||||||
|
isLoading: isLoadingOrgMembers
|
||||||
|
} = useScrollPagination(getOrgMembers, {
|
||||||
|
pageSize: 20,
|
||||||
|
params: {
|
||||||
|
orgPath: getOrgChildrenPath(currentOrg)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const [selected, setSelected] = useState<{ name: string; tmbId: string; avatar: string }[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedMembers(orgMembers.map((item) => item.tmbId));
|
setSelected(
|
||||||
|
orgMembers.map((item) => ({
|
||||||
|
name: item.memberName,
|
||||||
|
tmbId: item.tmbId,
|
||||||
|
avatar: item.avatar
|
||||||
|
}))
|
||||||
|
);
|
||||||
}, [orgMembers]);
|
}, [orgMembers]);
|
||||||
|
|
||||||
const [searchKey, setSearchKey] = useState('');
|
const [searchKey, setSearchKey] = useState('');
|
||||||
@@ -78,8 +96,8 @@ function OrgMemberManageModal({
|
|||||||
() => {
|
() => {
|
||||||
return putUpdateOrgMembers({
|
return putUpdateOrgMembers({
|
||||||
orgId: currentOrg._id,
|
orgId: currentOrg._id,
|
||||||
members: selectedMembers.map((tmbId) => ({
|
members: selected.map((member) => ({
|
||||||
tmbId
|
tmbId: member.tmbId
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -92,15 +110,25 @@ function OrgMemberManageModal({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const isSelected = (memberId: string) => {
|
const isSelected = (tmbId: string) => {
|
||||||
return selectedMembers.find((tmbId) => tmbId === memberId);
|
return selected.find((tmb) => tmb.tmbId === tmbId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggleSelect = (memberId: string) => {
|
const handleToggleSelect = (tmbId: string) => {
|
||||||
if (isSelected(memberId)) {
|
if (isSelected(tmbId)) {
|
||||||
setSelectedMembers((state) => state.filter((tmbId) => tmbId !== memberId));
|
setSelected((state) => state.filter((tmb) => tmb.tmbId !== tmbId));
|
||||||
|
// setSelectedTmbIds((state) => state.filter((tmbId) => tmbId !== memberId));
|
||||||
} else {
|
} else {
|
||||||
setSelectedMembers((state) => [...state, memberId]);
|
// setSelectedTmbIds((state) => [...state, memberId]);
|
||||||
|
const member = allMembers.find((item) => item.tmbId === tmbId)!;
|
||||||
|
setSelected((state) => [
|
||||||
|
...state,
|
||||||
|
{
|
||||||
|
name: member.memberName,
|
||||||
|
tmbId,
|
||||||
|
avatar: member.avatar
|
||||||
|
}
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -123,7 +151,14 @@ function OrgMemberManageModal({
|
|||||||
gridTemplateColumns="1fr 1fr"
|
gridTemplateColumns="1fr 1fr"
|
||||||
h={'100%'}
|
h={'100%'}
|
||||||
>
|
>
|
||||||
<Flex flexDirection="column" p="4" overflowY="auto" overflowX="hidden">
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
p="4"
|
||||||
|
overflowY="auto"
|
||||||
|
overflowX="hidden"
|
||||||
|
borderRight={'1px solid'}
|
||||||
|
borderColor={'myGray.200'}
|
||||||
|
>
|
||||||
<SearchInput
|
<SearchInput
|
||||||
placeholder={t('user:search_user')}
|
placeholder={t('user:search_user')}
|
||||||
fontSize="sm"
|
fontSize="sm"
|
||||||
@@ -132,7 +167,7 @@ function OrgMemberManageModal({
|
|||||||
setSearchKey(e.target.value);
|
setSearchKey(e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<MemberScrollData mt={3} flexGrow="1" overflow={'auto'}>
|
<MemberScrollData mt={3} flexGrow="1" overflow={'auto'} isLoading={isLoadingMembers}>
|
||||||
{filterMembers.map((member) => {
|
{filterMembers.map((member) => {
|
||||||
return (
|
return (
|
||||||
<HStack
|
<HStack
|
||||||
@@ -163,30 +198,34 @@ function OrgMemberManageModal({
|
|||||||
</Flex>
|
</Flex>
|
||||||
{/* <Flex mt={3} flexDirection="column" flexGrow="1" overflow={'auto'} maxH={'100%'}> */}
|
{/* <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 mt={3} flexGrow="1" overflow={'auto'}>
|
<OrgMemberScrollData
|
||||||
<Box mt={2}>{`${t('common:chosen')}:${selectedMembers.length}`}</Box>
|
mt={3}
|
||||||
{selectedMembers.map((tmbId) => {
|
flexGrow="1"
|
||||||
const member = allMembers.find((item) => item.tmbId === tmbId)!;
|
overflow={'auto'}
|
||||||
|
isLoading={isLoadingOrgMembers}
|
||||||
|
>
|
||||||
|
<Box mt={2}>{`${t('common:chosen')}:${selected.length}`}</Box>
|
||||||
|
{selected.map((member) => {
|
||||||
return (
|
return (
|
||||||
<HStack
|
<HStack
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
py="2"
|
py="2"
|
||||||
px={3}
|
px={3}
|
||||||
borderRadius={'md'}
|
borderRadius={'md'}
|
||||||
key={tmbId}
|
key={member.tmbId}
|
||||||
_hover={{ bg: 'myGray.50' }}
|
_hover={{ bg: 'myGray.50' }}
|
||||||
_notLast={{ mb: 2 }}
|
_notLast={{ mb: 2 }}
|
||||||
>
|
>
|
||||||
<HStack>
|
<HStack>
|
||||||
<Avatar src={member?.avatar} w="1.5rem" borderRadius={'md'} />
|
<Avatar src={member?.avatar} w="1.5rem" borderRadius={'md'} />
|
||||||
<Box>{member?.memberName}</Box>
|
<Box>{member?.name}</Box>
|
||||||
</HStack>
|
</HStack>
|
||||||
<MyIcon
|
<MyIcon
|
||||||
name={'common/closeLight'}
|
name={'common/closeLight'}
|
||||||
w={'1rem'}
|
w={'1rem'}
|
||||||
cursor={'pointer'}
|
cursor={'pointer'}
|
||||||
_hover={{ color: 'red.600' }}
|
_hover={{ color: 'red.600' }}
|
||||||
onClick={() => handleToggleSelect(tmbId)}
|
onClick={() => handleToggleSelect(member.tmbId)}
|
||||||
/>
|
/>
|
||||||
</HStack>
|
</HStack>
|
||||||
);
|
);
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { putMoveOrg } from '@/web/support/user/team/org/api';
|
import { getOrgList, putMoveOrg } from '@/web/support/user/team/org/api';
|
||||||
import { Button, ModalBody, ModalFooter } from '@chakra-ui/react';
|
import { Button, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||||
import type { OrgListItemType, OrgType } from '@fastgpt/global/support/user/team/org/type';
|
import type { OrgListItemType, OrgType } from '@fastgpt/global/support/user/team/org/type';
|
||||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
@@ -6,17 +6,15 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
|||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import OrgTree from './OrgTree';
|
import OrgTree from './OrgTree';
|
||||||
import dynamic from 'next/dynamic';
|
|
||||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
|
import useOrg from '@/web/support/user/team/org/hooks/useOrg';
|
||||||
|
|
||||||
function OrgMoveModal({
|
function OrgMoveModal({
|
||||||
movingOrg,
|
movingOrg,
|
||||||
orgs,
|
|
||||||
onClose,
|
onClose,
|
||||||
onSuccess
|
onSuccess
|
||||||
}: {
|
}: {
|
||||||
movingOrg: OrgListItemType;
|
movingOrg: OrgListItemType;
|
||||||
orgs: OrgListItemType[];
|
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSuccess: () => void;
|
onSuccess: () => void;
|
||||||
}) {
|
}) {
|
||||||
@@ -32,11 +30,6 @@ function OrgMoveModal({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const filterMovingOrgs = useMemo(
|
|
||||||
() => orgs.filter((org) => org._id !== movingOrg._id),
|
|
||||||
[movingOrg._id, orgs]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MyModal
|
<MyModal
|
||||||
isOpen
|
isOpen
|
||||||
@@ -46,11 +39,7 @@ function OrgMoveModal({
|
|||||||
iconColor="primary.600"
|
iconColor="primary.600"
|
||||||
>
|
>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<OrgTree
|
<OrgTree selectedOrg={selectedOrg} setSelectedOrg={setSelectedOrg} />
|
||||||
orgs={filterMovingOrgs}
|
|
||||||
selectedOrg={selectedOrg}
|
|
||||||
setSelectedOrg={setSelectedOrg}
|
|
||||||
/>
|
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button
|
<Button
|
||||||
|
@@ -1,29 +1,38 @@
|
|||||||
import { Box, HStack, VStack } from '@chakra-ui/react';
|
import { Box, HStack, VStack } from '@chakra-ui/react';
|
||||||
import type { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
import type { OrgListItemType, OrgType } from '@fastgpt/global/support/user/team/org/type';
|
||||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||||
import { useToggle } from 'ahooks';
|
import { useToggle } from 'ahooks';
|
||||||
import { useMemo } from 'react';
|
import { useState } from 'react';
|
||||||
import IconButton from './IconButton';
|
import IconButton from './IconButton';
|
||||||
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
|
import { getOrgList } from '@/web/support/user/team/org/api';
|
||||||
|
import { getChildrenByOrg } from '@fastgpt/service/support/permission/org/controllers';
|
||||||
import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant';
|
import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant';
|
||||||
|
|
||||||
function OrgTreeNode({
|
function OrgTreeNode({
|
||||||
org,
|
org,
|
||||||
list,
|
|
||||||
selectedOrg,
|
selectedOrg,
|
||||||
setSelectedOrg,
|
setSelectedOrg,
|
||||||
index = 0
|
index = 0
|
||||||
}: {
|
}: {
|
||||||
org: OrgType;
|
org: OrgListItemType;
|
||||||
list: OrgType[];
|
selectedOrg?: OrgListItemType;
|
||||||
selectedOrg?: OrgType;
|
setSelectedOrg: (org?: OrgListItemType) => void;
|
||||||
setSelectedOrg: (org?: OrgType) => void;
|
|
||||||
index?: number;
|
index?: number;
|
||||||
}) {
|
}) {
|
||||||
const children = useMemo(
|
|
||||||
() => list.filter((item) => item.path === getOrgChildrenPath(org)),
|
|
||||||
[org, list]
|
|
||||||
);
|
|
||||||
const [isExpanded, toggleIsExpanded] = useToggle(index === 0);
|
const [isExpanded, toggleIsExpanded] = useToggle(index === 0);
|
||||||
|
const [canBeExpanded, setCanBeExpanded] = useState(true);
|
||||||
|
const { data: orgs = [], runAsync: getOrgs } = useRequest2(() =>
|
||||||
|
getOrgList({ orgPath: getOrgChildrenPath(org) })
|
||||||
|
);
|
||||||
|
const onClickExpand = async () => {
|
||||||
|
const data = await getOrgs();
|
||||||
|
if (data.length < 1) {
|
||||||
|
setCanBeExpanded(false);
|
||||||
|
}
|
||||||
|
toggleIsExpanded.toggle();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box userSelect={'none'}>
|
<Box userSelect={'none'}>
|
||||||
@@ -34,7 +43,7 @@ function OrgTreeNode({
|
|||||||
pr={2}
|
pr={2}
|
||||||
pl={index === 0 ? '0.5rem' : `${1.75 * (index - 1) + 0.5}rem`}
|
pl={index === 0 ? '0.5rem' : `${1.75 * (index - 1) + 0.5}rem`}
|
||||||
cursor={'pointer'}
|
cursor={'pointer'}
|
||||||
{...(selectedOrg === org
|
{...(selectedOrg?._id === org._id
|
||||||
? {
|
? {
|
||||||
bg: 'primary.50 !important',
|
bg: 'primary.50 !important',
|
||||||
onClick: () => setSelectedOrg(undefined)
|
onClick: () => setSelectedOrg(undefined)
|
||||||
@@ -43,19 +52,17 @@ function OrgTreeNode({
|
|||||||
onClick: () => setSelectedOrg(org)
|
onClick: () => setSelectedOrg(org)
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{index > 0 && (
|
<IconButton
|
||||||
<IconButton
|
name={isExpanded ? 'common/downArrowFill' : 'common/rightArrowFill'}
|
||||||
name={isExpanded ? 'common/downArrowFill' : 'common/rightArrowFill'}
|
color={'myGray.500'}
|
||||||
color={'myGray.500'}
|
p={0}
|
||||||
p={0}
|
w={'1.25rem'}
|
||||||
w={'1.25rem'}
|
visibility={canBeExpanded ? 'visible' : 'hidden'}
|
||||||
visibility={children.length > 0 ? 'visible' : 'hidden'}
|
onClick={(e) => {
|
||||||
onClick={(e) => {
|
onClickExpand();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
toggleIsExpanded.toggle();
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<HStack
|
<HStack
|
||||||
flex={'1 0 0'}
|
flex={'1 0 0'}
|
||||||
onClick={() => setSelectedOrg(org)}
|
onClick={() => setSelectedOrg(org)}
|
||||||
@@ -67,13 +74,12 @@ function OrgTreeNode({
|
|||||||
</HStack>
|
</HStack>
|
||||||
</HStack>
|
</HStack>
|
||||||
{isExpanded &&
|
{isExpanded &&
|
||||||
children.length > 0 &&
|
orgs.length > 0 &&
|
||||||
children.map((child) => (
|
orgs.map((child) => (
|
||||||
<Box key={child._id} mt={0.5}>
|
<Box key={child._id} mt={0.5}>
|
||||||
<OrgTreeNode
|
<OrgTreeNode
|
||||||
org={child}
|
org={child}
|
||||||
index={index + 1}
|
index={index + 1}
|
||||||
list={list}
|
|
||||||
selectedOrg={selectedOrg}
|
selectedOrg={selectedOrg}
|
||||||
setSelectedOrg={setSelectedOrg}
|
setSelectedOrg={setSelectedOrg}
|
||||||
/>
|
/>
|
||||||
@@ -84,19 +90,29 @@ function OrgTreeNode({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function OrgTree({
|
function OrgTree({
|
||||||
orgs,
|
|
||||||
selectedOrg,
|
selectedOrg,
|
||||||
setSelectedOrg
|
setSelectedOrg
|
||||||
}: {
|
}: {
|
||||||
orgs: OrgType[];
|
selectedOrg?: OrgListItemType;
|
||||||
selectedOrg?: OrgType;
|
setSelectedOrg: (org?: OrgListItemType) => void;
|
||||||
setSelectedOrg: (org?: OrgType) => void;
|
|
||||||
}) {
|
}) {
|
||||||
const root = orgs[0];
|
const { userInfo } = useUserStore();
|
||||||
if (!root) return;
|
const root: OrgListItemType = {
|
||||||
|
_id: '',
|
||||||
|
path: '',
|
||||||
|
pathId: '',
|
||||||
|
name: userInfo?.team.teamName || '',
|
||||||
|
avatar: userInfo?.team.avatar || ''
|
||||||
|
} as any;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OrgTreeNode org={root} list={orgs} setSelectedOrg={setSelectedOrg} selectedOrg={selectedOrg} />
|
<OrgTreeNode
|
||||||
|
key={'root'}
|
||||||
|
org={root}
|
||||||
|
selectedOrg={selectedOrg}
|
||||||
|
setSelectedOrg={setSelectedOrg}
|
||||||
|
index={1}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -24,12 +24,7 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
|||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import MemberTag from '@/components/support/user/team/Info/MemberTag';
|
import MemberTag from '@/components/support/user/team/Info/MemberTag';
|
||||||
import {
|
import { deleteOrg, deleteOrgMember } from '@/web/support/user/team/org/api';
|
||||||
deleteOrg,
|
|
||||||
deleteOrgMember,
|
|
||||||
getOrgList,
|
|
||||||
getOrgMembers
|
|
||||||
} from '@/web/support/user/team/org/api';
|
|
||||||
|
|
||||||
import IconButton from './IconButton';
|
import IconButton from './IconButton';
|
||||||
import { defaultOrgForm, type OrgFormType } from './OrgInfoModal';
|
import { defaultOrgForm, type OrgFormType } from './OrgInfoModal';
|
||||||
@@ -43,6 +38,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
|
|||||||
import { delRemoveMember } from '@/web/support/user/team/api';
|
import { delRemoveMember } from '@/web/support/user/team/api';
|
||||||
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||||
|
import useOrg from '@/web/support/user/team/org/hooks/useOrg';
|
||||||
|
|
||||||
const OrgInfoModal = dynamic(() => import('./OrgInfoModal'));
|
const OrgInfoModal = dynamic(() => import('./OrgInfoModal'));
|
||||||
const OrgMemberManageModal = dynamic(() => import('./OrgMemberManageModal'));
|
const OrgMemberManageModal = dynamic(() => import('./OrgMemberManageModal'));
|
||||||
@@ -82,51 +78,25 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
const { userInfo, isTeamAdmin } = useUserStore();
|
const { userInfo, isTeamAdmin } = useUserStore();
|
||||||
const { feConfigs } = useSystemStore();
|
const { feConfigs } = useSystemStore();
|
||||||
const isSyncMember = feConfigs.register_method?.includes('sync');
|
const isSyncMember = feConfigs.register_method?.includes('sync');
|
||||||
|
|
||||||
const [searchOrg, setSearchOrg] = useState('');
|
|
||||||
|
|
||||||
const [parentId, setParentId] = useState<ParentIdType>();
|
|
||||||
const [currentOrg, setCurrentOrg] = useState<OrgListItemType>();
|
|
||||||
|
|
||||||
// 用于 org 层级
|
|
||||||
const [orgStack, setOrgStack] = useState<OrgListItemType[]>([]);
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: orgs = [],
|
|
||||||
loading: isLoadingOrgs,
|
|
||||||
refresh: refetchOrgs
|
|
||||||
} = useRequest2(
|
|
||||||
() => {
|
|
||||||
return getOrgList(parentId);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
manual: false,
|
|
||||||
refreshDeps: [userInfo?.team?.teamId, parentId]
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const paths = useMemo(() => {
|
|
||||||
if (!currentOrg) return [];
|
|
||||||
return orgStack
|
|
||||||
.map((org) => {
|
|
||||||
return {
|
|
||||||
parentId: getOrgChildrenPath(org),
|
|
||||||
parentName: org.name
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter(Boolean) as ParentTreePathItemType[];
|
|
||||||
}, [currentOrg, orgStack]);
|
|
||||||
|
|
||||||
const onClickOrg = (org: OrgListItemType) => {
|
|
||||||
setParentId(currentOrg?._id);
|
|
||||||
setOrgStack([...orgStack, org]);
|
|
||||||
setCurrentOrg(org);
|
|
||||||
};
|
|
||||||
|
|
||||||
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 {
|
||||||
|
currentOrg,
|
||||||
|
orgs,
|
||||||
|
isLoadingOrgs,
|
||||||
|
paths,
|
||||||
|
onClickOrg,
|
||||||
|
members,
|
||||||
|
MemberScrollData,
|
||||||
|
onPathClick,
|
||||||
|
refresh,
|
||||||
|
updateCurrentOrg
|
||||||
|
} = useOrg();
|
||||||
|
|
||||||
// Delete org
|
// Delete org
|
||||||
const { ConfirmModal: ConfirmDeleteOrgModal, openConfirm: openDeleteOrgModal } = useConfirm({
|
const { ConfirmModal: ConfirmDeleteOrgModal, openConfirm: openDeleteOrgModal } = useConfirm({
|
||||||
type: 'delete',
|
type: 'delete',
|
||||||
@@ -134,17 +104,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
});
|
});
|
||||||
const deleteOrgHandler = (orgId: string) => openDeleteOrgModal(() => deleteOrgReq(orgId))();
|
const deleteOrgHandler = (orgId: string) => openDeleteOrgModal(() => deleteOrgReq(orgId))();
|
||||||
const { runAsync: deleteOrgReq } = useRequest2(deleteOrg, {
|
const { runAsync: deleteOrgReq } = useRequest2(deleteOrg, {
|
||||||
onSuccess: () => {
|
onSuccess: refresh
|
||||||
refetchOrgs();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data: members = [], ScrollData: MemberScrollData } = useScrollPagination(getOrgMembers, {
|
|
||||||
pageSize: 20,
|
|
||||||
params: {
|
|
||||||
orgId: currentOrg?._id
|
|
||||||
},
|
|
||||||
refreshDeps: [currentOrg?._id]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Delete member
|
// Delete member
|
||||||
@@ -159,15 +119,11 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { runAsync: deleteMemberReq } = useRequest2(deleteOrgMember, {
|
const { runAsync: deleteMemberReq } = useRequest2(deleteOrgMember, {
|
||||||
onSuccess: () => {
|
onSuccess: refresh
|
||||||
refetchOrgs();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { runAsync: deleteMemberFromTeamReq } = useRequest2(delRemoveMember, {
|
const { runAsync: deleteMemberFromTeamReq } = useRequest2(delRemoveMember, {
|
||||||
onSuccess: () => {
|
onSuccess: refresh
|
||||||
refetchOrgs();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const searchedOrgs = useMemo(() => {
|
const searchedOrgs = useMemo(() => {
|
||||||
@@ -196,7 +152,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
isLoading={isLoadingOrgs}
|
isLoading={isLoadingOrgs}
|
||||||
>
|
>
|
||||||
<Box mb={3}>
|
<Box mb={3}>
|
||||||
<Path paths={paths} rootName={userInfo?.team?.teamName} />
|
<Path paths={paths} rootName={userInfo?.team?.teamName} onClick={onPathClick} />
|
||||||
</Box>
|
</Box>
|
||||||
<Flex flex={'1 0 0'} h={0} w={'100%'} gap={'4'}>
|
<Flex flex={'1 0 0'} h={0} w={'100%'} gap={'4'}>
|
||||||
<MemberScrollData flex="1">
|
<MemberScrollData flex="1">
|
||||||
@@ -356,8 +312,14 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
label: t('account_team:delete_from_org'),
|
label: t('account_team:delete_from_org'),
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
openDeleteMemberFromOrgModal(
|
openDeleteMemberFromOrgModal(
|
||||||
() =>
|
() => {
|
||||||
deleteMemberReq(currentOrg._id, member.tmbId),
|
if (currentOrg) {
|
||||||
|
return deleteMemberReq(
|
||||||
|
currentOrg._id,
|
||||||
|
member.tmbId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
undefined,
|
undefined,
|
||||||
t('account_team:confirm_delete_from_org', {
|
t('account_team:confirm_delete_from_org', {
|
||||||
username: member.memberName
|
username: member.memberName
|
||||||
@@ -383,20 +345,15 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
{!isSyncMember && (
|
{!isSyncMember && (
|
||||||
<VStack w={'180px'} alignItems={'start'}>
|
<VStack w={'180px'} alignItems={'start'}>
|
||||||
<HStack gap={'6px'}>
|
<HStack gap={'6px'}>
|
||||||
<Avatar
|
<Avatar src={currentOrg.avatar} w={'1rem'} h={'1rem'} rounded={'xs'} />
|
||||||
src={currentOrg?.avatar || userInfo?.team.avatar}
|
|
||||||
w={'1rem'}
|
|
||||||
h={'1rem'}
|
|
||||||
rounded={'xs'}
|
|
||||||
/>
|
|
||||||
<Box fontWeight={500} color={'myGray.900'}>
|
<Box fontWeight={500} color={'myGray.900'}>
|
||||||
{currentOrg?.name || userInfo?.team.teamName}
|
{currentOrg.name}
|
||||||
</Box>
|
</Box>
|
||||||
{currentOrg && currentOrg?.path !== '' && (
|
{currentOrg?.path !== '' && (
|
||||||
<IconButton name="edit" onClick={() => setEditOrg(currentOrg)} />
|
<IconButton name="edit" onClick={() => setEditOrg(currentOrg)} />
|
||||||
)}
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
{currentOrg && (
|
{currentOrg?.path !== '' && (
|
||||||
<Box fontSize={'xs'}>{currentOrg?.description || t('common:common.no_intro')}</Box>
|
<Box fontSize={'xs'}>{currentOrg?.description || t('common:common.no_intro')}</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -413,7 +370,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEditOrg({
|
setEditOrg({
|
||||||
...defaultOrgForm,
|
...defaultOrgForm,
|
||||||
parentId: currentOrg?._id
|
path: currentOrg.path
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -422,7 +379,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
text={t('account_team:manage_member')}
|
text={t('account_team:manage_member')}
|
||||||
onClick={() => setManageMemberOrg(currentOrg)}
|
onClick={() => setManageMemberOrg(currentOrg)}
|
||||||
/>
|
/>
|
||||||
{currentOrg && currentOrg?.path !== '' && (
|
{currentOrg?.path !== '' && (
|
||||||
<>
|
<>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon="common/file/move"
|
icon="common/file/move"
|
||||||
@@ -447,21 +404,21 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
<OrgInfoModal
|
<OrgInfoModal
|
||||||
editOrg={editOrg}
|
editOrg={editOrg}
|
||||||
onClose={() => setEditOrg(undefined)}
|
onClose={() => setEditOrg(undefined)}
|
||||||
onSuccess={refetchOrgs}
|
onSuccess={refresh}
|
||||||
|
updateCurrentOrg={updateCurrentOrg}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!!movingOrg && (
|
{!!movingOrg && (
|
||||||
<OrgMoveModal
|
<OrgMoveModal
|
||||||
orgs={orgs}
|
|
||||||
movingOrg={movingOrg}
|
movingOrg={movingOrg}
|
||||||
onClose={() => setMovingOrg(undefined)}
|
onClose={() => setMovingOrg(undefined)}
|
||||||
onSuccess={refetchOrgs}
|
onSuccess={refresh}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!!manageMemberOrg && (
|
{!!manageMemberOrg && (
|
||||||
<OrgMemberManageModal
|
<OrgMemberManageModal
|
||||||
currentOrg={manageMemberOrg}
|
currentOrg={manageMemberOrg}
|
||||||
refetchOrgs={refetchOrgs}
|
refetchOrgs={refresh}
|
||||||
onClose={() => setManageMemberOrg(undefined)}
|
onClose={() => setManageMemberOrg(undefined)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@@ -23,21 +23,18 @@ const EditInfoModal = dynamic(() => import('./EditInfoModal'));
|
|||||||
type TeamModalContextType = {
|
type TeamModalContextType = {
|
||||||
myTeams: TeamTmbItemType[];
|
myTeams: TeamTmbItemType[];
|
||||||
members: TeamMemberItemType[];
|
members: TeamMemberItemType[];
|
||||||
groups: MemberGroupListType;
|
|
||||||
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;
|
refetchMembers: () => void;
|
||||||
refetchTeams: () => void;
|
refetchTeams: () => void;
|
||||||
refetchGroups: () => void;
|
|
||||||
teamSize: number;
|
teamSize: number;
|
||||||
MemberScrollData: ReturnType<typeof useScrollPagination>['ScrollData'];
|
MemberScrollData: ReturnType<typeof useScrollPagination>['ScrollData'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TeamContext = createContext<TeamModalContextType>({
|
export const TeamContext = createContext<TeamModalContextType>({
|
||||||
myTeams: [],
|
myTeams: [],
|
||||||
groups: [],
|
|
||||||
members: [],
|
members: [],
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
onSwitchTeam: function (_teamId: string): void {
|
onSwitchTeam: function (_teamId: string): void {
|
||||||
@@ -52,9 +49,6 @@ export const TeamContext = createContext<TeamModalContextType>({
|
|||||||
refetchMembers: function (): void {
|
refetchMembers: function (): void {
|
||||||
throw new Error('Function not implemented.');
|
throw new Error('Function not implemented.');
|
||||||
},
|
},
|
||||||
refetchGroups: function (): void {
|
|
||||||
throw new Error('Function not implemented.');
|
|
||||||
},
|
|
||||||
teamSize: 0,
|
teamSize: 0,
|
||||||
MemberScrollData: () => <></>
|
MemberScrollData: () => <></>
|
||||||
});
|
});
|
||||||
@@ -110,16 +104,7 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const isLoading = isLoadingTeams || isSwitchingTeam || loadingMembers;
|
||||||
data: groups = [],
|
|
||||||
loading: isLoadingGroups,
|
|
||||||
refresh: refetchGroups
|
|
||||||
} = useRequest2(getGroupList, {
|
|
||||||
manual: false,
|
|
||||||
refreshDeps: [userInfo?.team?.teamId]
|
|
||||||
});
|
|
||||||
|
|
||||||
const isLoading = isLoadingTeams || isSwitchingTeam || loadingMembers || isLoadingGroups;
|
|
||||||
|
|
||||||
const contextValue = {
|
const contextValue = {
|
||||||
myTeams,
|
myTeams,
|
||||||
@@ -131,8 +116,6 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
|
|||||||
setEditTeamData,
|
setEditTeamData,
|
||||||
members,
|
members,
|
||||||
refetchMembers,
|
refetchMembers,
|
||||||
groups,
|
|
||||||
refetchGroups,
|
|
||||||
teamSize: teamMemberCountData?.count || 0,
|
teamSize: teamMemberCountData?.count || 0,
|
||||||
MemberScrollData
|
MemberScrollData
|
||||||
};
|
};
|
||||||
|
@@ -10,8 +10,8 @@ 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 = (parentId: ParentIdType) =>
|
export const getOrgList = (params: { orgPath: string; getPermission?: boolean }) =>
|
||||||
GET<OrgListItemType[]>(`/proApi/support/user/team/org/list`, { parentId });
|
GET<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);
|
||||||
@@ -19,19 +19,17 @@ export const postCreateOrg = (data: postCreateOrgData) =>
|
|||||||
export const deleteOrg = (orgId: string) =>
|
export const deleteOrg = (orgId: string) =>
|
||||||
DELETE('/proApi/support/user/team/org/delete', { orgId });
|
DELETE('/proApi/support/user/team/org/delete', { orgId });
|
||||||
|
|
||||||
export const deleteOrgMember = (orgId: string, tmbId: string) =>
|
|
||||||
DELETE('/proApi/support/user/team/org/deleteMember', { orgId, tmbId });
|
|
||||||
|
|
||||||
export const putMoveOrg = (data: putMoveOrgType) => PUT('/proApi/support/user/team/org/move', data);
|
export const putMoveOrg = (data: putMoveOrgType) => PUT('/proApi/support/user/team/org/move', data);
|
||||||
|
|
||||||
export const putUpdateOrg = (data: putUpdateOrgData) =>
|
export const putUpdateOrg = (data: putUpdateOrgData) =>
|
||||||
PUT('/proApi/support/user/team/org/update', data);
|
PUT('/proApi/support/user/team/org/update', data);
|
||||||
|
|
||||||
|
// org members
|
||||||
export const putUpdateOrgMembers = (data: putUpdateOrgMembersData) =>
|
export const putUpdateOrgMembers = (data: putUpdateOrgMembersData) =>
|
||||||
PUT('/proApi/support/user/team/org/updateMembers', data);
|
PUT('/proApi/support/user/team/org/updateMembers', data);
|
||||||
|
|
||||||
// export const putChnageOrgOwner = (data: putChnageOrgOwnerData) =>
|
export const getOrgMembers = (data: PaginationProps<{ orgPath?: string }>) =>
|
||||||
// PUT('/proApi/support/user/team/org/changeOwner', data);
|
|
||||||
|
|
||||||
export const getOrgMembers = (data: PaginationProps<{ orgId: string }>) =>
|
|
||||||
GET<PaginationResponse<TeamMemberItemType>>(`/proApi/support/user/team/org/members`, data);
|
GET<PaginationResponse<TeamMemberItemType>>(`/proApi/support/user/team/org/members`, data);
|
||||||
|
|
||||||
|
export const deleteOrgMember = (orgId: string, tmbId: string) =>
|
||||||
|
DELETE('/proApi/support/user/team/org/deleteMember', { orgId, tmbId });
|
||||||
|
108
projects/app/src/web/support/user/team/org/hooks/useOrg.tsx
Normal file
108
projects/app/src/web/support/user/team/org/hooks/useOrg.tsx
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
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 { 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';
|
||||||
|
|
||||||
|
function useOrg({ getPermission = true }: { getPermission?: boolean } = {}) {
|
||||||
|
const [orgStack, setOrgStack] = useState<OrgListItemType[]>([]);
|
||||||
|
|
||||||
|
const { userInfo } = useUserStore();
|
||||||
|
|
||||||
|
const path = useMemo(
|
||||||
|
() => (orgStack.length ? getOrgChildrenPath(orgStack[orgStack.length - 1]) : ''),
|
||||||
|
[orgStack]
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentOrg = useMemo(() => {
|
||||||
|
return (
|
||||||
|
orgStack.at(-1) ??
|
||||||
|
({
|
||||||
|
_id: '',
|
||||||
|
path: '',
|
||||||
|
pathId: '',
|
||||||
|
avatar: userInfo?.team.avatar,
|
||||||
|
name: userInfo?.team.teamName
|
||||||
|
} as OrgListItemType) // root org
|
||||||
|
);
|
||||||
|
}, [orgStack, userInfo?.team.avatar, userInfo?.team.teamName]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: orgs = [],
|
||||||
|
loading: isLoadingOrgs,
|
||||||
|
refresh: refetchOrgs
|
||||||
|
} = useRequest2(() => getOrgList({ orgPath: path, getPermission }), {
|
||||||
|
manual: false,
|
||||||
|
refreshDeps: [userInfo?.team?.teamId, path]
|
||||||
|
});
|
||||||
|
|
||||||
|
const paths = useMemo(() => {
|
||||||
|
if (!currentOrg) return [];
|
||||||
|
return orgStack
|
||||||
|
.map((org) => {
|
||||||
|
return {
|
||||||
|
parentId: getOrgChildrenPath(org),
|
||||||
|
parentName: org.name
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean) as ParentTreePathItemType[];
|
||||||
|
}, [currentOrg, orgStack]);
|
||||||
|
|
||||||
|
const onClickOrg = (org: OrgListItemType) => {
|
||||||
|
setOrgStack([...orgStack, org]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: members = [],
|
||||||
|
ScrollData: MemberScrollData,
|
||||||
|
refreshList: refetchMembers
|
||||||
|
} = useScrollPagination(getOrgMembers, {
|
||||||
|
pageSize: 20,
|
||||||
|
params: {
|
||||||
|
orgPath: path
|
||||||
|
},
|
||||||
|
refreshDeps: [path]
|
||||||
|
});
|
||||||
|
|
||||||
|
const onPathClick = (path: string) => {
|
||||||
|
const pathIds = path.split('/');
|
||||||
|
setOrgStack(orgStack.filter((org) => pathIds.includes(org.pathId)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const refresh = () => {
|
||||||
|
refetchOrgs();
|
||||||
|
refetchMembers();
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateCurrentOrg = (data: { name?: string; description?: string; avatar?: string }) => {
|
||||||
|
if (currentOrg.path === '') return;
|
||||||
|
setOrgStack([
|
||||||
|
...orgStack.slice(0, -1),
|
||||||
|
{
|
||||||
|
...currentOrg,
|
||||||
|
name: data.name || currentOrg.name,
|
||||||
|
description: data.description || currentOrg.description,
|
||||||
|
avatar: data.avatar || currentOrg.avatar
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
orgStack,
|
||||||
|
currentOrg,
|
||||||
|
orgs,
|
||||||
|
isLoadingOrgs,
|
||||||
|
paths,
|
||||||
|
onClickOrg,
|
||||||
|
members,
|
||||||
|
MemberScrollData,
|
||||||
|
onPathClick,
|
||||||
|
refresh,
|
||||||
|
updateCurrentOrg
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useOrg;
|
Reference in New Issue
Block a user