4.8.19-feature (#3636)

* feat: sync org from wecom, pref: member list pagination (#3549)

* feat: sync org

* chore: fe

* chore: loading

* chore: type

* pref: team member list change to pagination. Edit a sort of list apis.

* feat: member update avatar

* chore: user avatar move to tmb

* chore: init scripts move user avatar

* chore: sourceMember

* fix: list api sourceMember

* fix: member sync

* fix: pagination

* chore: adjust code

* chore: move changeOwner to pro

* chore: init v4819 script

* chore: adjust code

* chore: UserBox

* perf: scroll page code

* perf: list data

* docs:更新用户答疑 (#3576)

* docs: add custom uid docs (#3572)

* fix: pagination bug (#3577)

* 4.8.19 test (#3584)

* faet: dataset search filter

* fix: scroll page

* fix: collection list api old version (#3591)

* fix: collection list api format

* fix: type error of addSourceMemeber

* fix: scroll fetch (#3592)

* fix: yuque dataset file folder can enter (#3593)

* perf: load members;perf: yuque load;fix: workflow llm params cannot close (#3594)

* chat openapi doc

* feat: dataset openapi doc

* perf: load members

* perf: member load code

* perf: yuque load

* fix: workflow llm params cannot close

* fix: api dataset reference tag preview (#3600)

* perf: doc

* feat: chat page config

* fix: http parse (#3634)

* update doc

* fix: http parse

* fix code run node reset template (#3633)

Co-authored-by: Archer <545436317@qq.com>

* docs:faq (#3627)

* docs:faq

* docsFix

* perf: sleep plugin

* fix: selector

---------

Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
Co-authored-by: Jiangween <145003935+Jiangween@users.noreply.github.com>
Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
Archer
2025-01-20 19:42:33 +08:00
committed by GitHub
parent 9f33729ca9
commit 3c97757e4d
170 changed files with 2317 additions and 1615 deletions

View File

@@ -0,0 +1,31 @@
import { IconProps } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import type { IconNameType } from '@fastgpt/web/components/common/Icon/type';
function IconButton({
name,
w = '1rem',
h = '1rem',
...props
}: {
name: IconNameType;
} & IconProps) {
return (
<MyIcon
name={name}
w={w}
h={h}
transition={'background 0.1s'}
cursor={'pointer'}
p="1"
rounded={'sm'}
_hover={{
bg: 'myGray.05',
color: 'primary.600'
}}
{...props}
/>
);
}
export default IconButton;

View File

@@ -0,0 +1,162 @@
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { postCreateOrg, putUpdateOrg } from '@/web/support/user/team/org/api';
import { Button, HStack, Input, ModalBody, ModalFooter, Textarea } from '@chakra-ui/react';
import { DEFAULT_ORG_AVATAR } from '@fastgpt/global/common/system/constants';
import Avatar from '@fastgpt/web/components/common/Avatar';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useTranslation } from 'next-i18next';
import dynamic from 'next/dynamic';
import { useForm } from 'react-hook-form';
export type OrgFormType = {
_id: string;
avatar: string;
description?: string;
name: string;
path: string;
parentId?: string;
};
export const defaultOrgForm: OrgFormType = {
_id: '',
avatar: '',
description: '',
name: '',
path: ''
};
function OrgInfoModal({
editOrg,
onClose,
onSuccess
}: {
editOrg: OrgFormType;
onClose: () => void;
onSuccess: () => void;
}) {
const { t } = useTranslation();
const isEdit = !!editOrg._id;
const { register, handleSubmit, setValue, watch } = useForm<OrgFormType>({
defaultValues: {
name: editOrg.name,
avatar: editOrg.avatar,
description: editOrg.description
}
});
const avatar = watch('avatar');
const { run: onCreate, loading: isLoadingCreate } = useRequest2(
async (data: OrgFormType) => {
if (!editOrg.parentId) return;
return postCreateOrg({
name: data.name,
avatar: data.avatar,
parentId: editOrg.parentId,
description: data.description
});
},
{
successToast: t('common:common.Create Success'),
onSuccess: () => {
onClose();
onSuccess();
}
}
);
const { run: onUpdate, loading: isLoadingUpdate } = useRequest2(
async (data: OrgFormType) => {
if (!editOrg._id) return;
return putUpdateOrg({
orgId: editOrg._id,
name: data.name,
avatar: data.avatar,
description: data.description
});
},
{
successToast: t('common:common.Update Success'),
onSuccess: () => {
onClose();
onSuccess();
}
}
);
const {
File: AvatarSelect,
onOpen: onOpenSelectAvatar,
onSelectImage
} = useSelectFile({
fileType: '.jpg, .jpeg, .png',
multiple: false
});
const { loading: uploadingAvatar, run: onSelectAvatar } = useRequest2(
async (file: File[]) => {
return onSelectImage(file, {
maxW: 300,
maxH: 300
});
},
{
onSuccess: (src: string) => {
setValue('avatar', src);
}
}
);
const isLoading = uploadingAvatar || isLoadingUpdate || isLoadingCreate;
return (
<MyModal
isOpen
onClose={onClose}
title={isEdit ? t('account_team:edit_org_info') : t('account_team:create_org')}
iconSrc={'modal/edit'}
>
<ModalBody flex={1} overflow={'auto'} display={'flex'} flexDirection={'column'} gap={4}>
<FormLabel w="80px">{t('user:team.avatar_and_name')}</FormLabel>
<HStack>
<Avatar
src={avatar || DEFAULT_ORG_AVATAR}
onClick={onOpenSelectAvatar}
cursor={'pointer'}
borderRadius={'md'}
/>
<Input
bgColor="myGray.50"
{...register('name', { required: true })}
placeholder={t('account_team:org_name')}
/>
</HStack>
<FormLabel w="80px">{t('account_team:org_description')}</FormLabel>
<Textarea
bgColor="myGray.50"
{...register('description')}
placeholder={t('account_team:org_description')}
/>
</ModalBody>
<ModalFooter alignItems="flex-end">
<Button
isLoading={isLoading}
onClick={handleSubmit((data) => {
if (isEdit) {
onUpdate(data);
} else {
onCreate(data);
}
})}
>
{isEdit ? t('common:common.Save') : t('common:new_create')}
</Button>
</ModalFooter>
<AvatarSelect onSelect={onSelectAvatar} />
</MyModal>
);
}
export default OrgInfoModal;

View File

@@ -0,0 +1,200 @@
import { putUpdateOrgMembers } from '@/web/support/user/team/org/api';
import {
Box,
Button,
Checkbox,
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';
import type { IconNameType } from '@fastgpt/web/components/common/Icon/type';
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 type React from 'react';
import { useMemo, useState } from 'react';
import { useContextSelector } from 'use-context-selector';
import { TeamContext } from '../context';
import { OrgType } from '@fastgpt/global/support/user/team/org/type';
export type GroupFormType = {
members: {
tmbId: string;
role: `${GroupMemberRole}`;
}[];
};
function CheckboxIcon({
name
}: {
isChecked?: boolean;
isIndeterminate?: boolean;
name: IconNameType;
}) {
return <MyIcon name={name} w="12px" />;
}
function OrgMemberManageModal({
currentOrg,
refetchOrgs,
onClose
}: {
currentOrg: OrgType;
refetchOrgs: () => void;
onClose: () => void;
}) {
const { t } = useTranslation();
const { members: allMembers, MemberScrollData } = useContextSelector(TeamContext, (v) => v);
const [selectedMembers, setSelectedMembers] = useState<string[]>(
currentOrg.members.map((item) => item.tmbId)
);
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(
() => {
return putUpdateOrgMembers({
orgId: currentOrg._id,
members: selectedMembers.map((tmbId) => ({
tmbId
}))
});
},
{
successToast: t('common:common.Update Success'),
onSuccess() {
refetchOrgs();
onClose();
}
}
);
const isSelected = (memberId: string) => {
return selectedMembers.find((tmbId) => tmbId === memberId);
};
const handleToggleSelect = (memberId: string) => {
if (isSelected(memberId)) {
setSelectedMembers((state) => state.filter((tmbId) => tmbId !== memberId));
} else {
setSelectedMembers((state) => [...state, memberId]);
}
};
const isLoading = isLoadingUpdate;
return (
<MyModal
isOpen
title={t('user:team.group.manage_member')}
iconSrc={currentOrg?.avatar}
minW="800px"
h={'100%'}
isCentered
>
<ModalBody flex={1}>
<Grid
border="1px solid"
borderColor="myGray.200"
borderRadius="0.5rem"
gridTemplateColumns="1fr 1fr"
h={'100%'}
>
<Flex flexDirection="column" p="4">
<SearchInput
placeholder={t('user:search_user')}
fontSize="sm"
bg={'myGray.50'}
onChange={(e) => {
setSearchKey(e.target.value);
}}
/>
<Flex flexDirection="column" mt={3} flexGrow="1" overflow={'auto'} maxH={'400px'}>
<MemberScrollData>
{filterMembers.map((member) => {
return (
<HStack
py="2"
px={3}
borderRadius={'md'}
alignItems="center"
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>
);
})}
</MemberScrollData>
</Flex>
</Flex>
<Flex borderLeft="1px" borderColor="myGray.200" flexDirection="column" p="4" h={'100%'}>
<Box mt={2}>{`${t('common:chosen')}:${selectedMembers.length}`}</Box>
<Flex mt={3} flexDirection="column" flexGrow="1" overflow={'auto'} maxH={'400px'}>
{selectedMembers.map((tmbId) => {
const member = allMembers.find((item) => item.tmbId === tmbId)!;
return (
<HStack
justifyContent="space-between"
py="2"
px={3}
borderRadius={'md'}
key={tmbId}
_hover={{ bg: 'myGray.50' }}
_notLast={{ mb: 2 }}
>
<HStack>
<Avatar src={member?.avatar} w="1.5rem" borderRadius={'md'} />
<Box>{member?.memberName}</Box>
</HStack>
<MyIcon
name={'common/closeLight'}
w={'1rem'}
cursor={'pointer'}
_hover={{ color: 'red.600' }}
onClick={() => handleToggleSelect(tmbId)}
/>
</HStack>
);
})}
</Flex>
</Flex>
</Grid>
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common:common.Close')}
</Button>
<Button isLoading={isLoading} onClick={onUpdate}>
{t('common:common.Save')}
</Button>
</ModalFooter>
</MyModal>
);
}
export default OrgMemberManageModal;

View File

@@ -0,0 +1,74 @@
import { putMoveOrg } from '@/web/support/user/team/org/api';
import { Button, ModalBody, ModalFooter } from '@chakra-ui/react';
import type { OrgType } from '@fastgpt/global/support/user/team/org/type';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useTranslation } from 'next-i18next';
import { useMemo, useState } from 'react';
import OrgTree from './OrgTree';
import dynamic from 'next/dynamic';
import { useUserStore } from '@/web/support/user/useUserStore';
function OrgMoveModal({
movingOrg,
orgs,
onClose,
onSuccess
}: {
movingOrg: OrgType;
orgs: OrgType[];
onClose: () => void;
onSuccess: () => void;
}) {
const { t } = useTranslation();
const [selectedOrg, setSelectedOrg] = useState<OrgType>();
const { userInfo } = useUserStore();
const team = userInfo?.team!;
const { runAsync: onMoveOrg, loading } = useRequest2(putMoveOrg, {
onSuccess: () => {
onClose();
onSuccess();
}
});
const filterMovingOrgs = useMemo(
() => orgs.filter((org) => org._id !== movingOrg._id),
[movingOrg._id, orgs]
);
return (
<MyModal
isOpen
onClose={onClose}
title={t('account_team:move_org')}
iconSrc="common/file/move"
iconColor="primary.600"
>
<ModalBody>
<OrgTree
orgs={filterMovingOrgs}
selectedOrg={selectedOrg}
setSelectedOrg={setSelectedOrg}
/>
</ModalBody>
<ModalFooter>
<Button
isDisabled={!selectedOrg}
isLoading={loading}
onClick={() => {
if (!selectedOrg) return;
return onMoveOrg({
orgId: movingOrg._id,
targetOrgId: selectedOrg._id
});
}}
>
{t('common:common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
);
}
export default OrgMoveModal;

View File

@@ -0,0 +1,103 @@
import { Box, HStack, VStack } from '@chakra-ui/react';
import type { OrgType } from '@fastgpt/global/support/user/team/org/type';
import Avatar from '@fastgpt/web/components/common/Avatar';
import { useToggle } from 'ahooks';
import { useMemo } from 'react';
import IconButton from './IconButton';
import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant';
function OrgTreeNode({
org,
list,
selectedOrg,
setSelectedOrg,
index = 0
}: {
org: OrgType;
list: OrgType[];
selectedOrg?: OrgType;
setSelectedOrg: (org?: OrgType) => void;
index?: number;
}) {
const children = useMemo(
() => list.filter((item) => item.path === getOrgChildrenPath(org)),
[org, list]
);
const [isExpanded, toggleIsExpanded] = useToggle(index === 0);
return (
<Box userSelect={'none'}>
<HStack
borderRadius="sm"
_hover={{ bg: 'myGray.100' }}
py={1}
pr={2}
pl={index === 0 ? '0.5rem' : `${1.75 * (index - 1) + 0.5}rem`}
cursor={'pointer'}
{...(selectedOrg === org
? {
bg: 'primary.50 !important',
onClick: () => setSelectedOrg(undefined)
}
: {
onClick: () => setSelectedOrg(org)
})}
>
{index > 0 && (
<IconButton
name={isExpanded ? 'common/downArrowFill' : 'common/rightArrowFill'}
color={'myGray.500'}
p={0}
w={'1.25rem'}
visibility={children.length > 0 ? 'visible' : 'hidden'}
onClick={(e) => {
e.stopPropagation();
toggleIsExpanded.toggle();
}}
/>
)}
<HStack
flex={'1 0 0'}
onClick={() => setSelectedOrg(org)}
cursor={'pointer'}
borderRadius={'xs'}
>
<Avatar src={org.avatar} w={'1.25rem'} borderRadius={'xs'} />
<Box>{org.name}</Box>
</HStack>
</HStack>
{isExpanded &&
children.length > 0 &&
children.map((child) => (
<Box key={child._id} mt={0.5}>
<OrgTreeNode
org={child}
index={index + 1}
list={list}
selectedOrg={selectedOrg}
setSelectedOrg={setSelectedOrg}
/>
</Box>
))}
</Box>
);
}
function OrgTree({
orgs,
selectedOrg,
setSelectedOrg
}: {
orgs: OrgType[];
selectedOrg?: OrgType;
setSelectedOrg: (org?: OrgType) => void;
}) {
const root = orgs[0];
if (!root) return;
return (
<OrgTreeNode org={root} list={orgs} setSelectedOrg={setSelectedOrg} selectedOrg={selectedOrg} />
);
}
export default OrgTree;

View File

@@ -0,0 +1,369 @@
import { useUserStore } from '@/web/support/user/useUserStore';
import {
Box,
Divider,
Flex,
HStack,
Table,
TableContainer,
Tag,
Tbody,
Td,
Th,
Thead,
Tr,
VStack
} from '@chakra-ui/react';
import type { OrgType } from '@fastgpt/global/support/user/team/org/type';
import Avatar from '@fastgpt/web/components/common/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
import type { IconNameType } from '@fastgpt/web/components/common/Icon/type';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useTranslation } from 'next-i18next';
import { useMemo, useState } from 'react';
import { useContextSelector } from 'use-context-selector';
import MemberTag from '@/components/support/user/team/Info/MemberTag';
import { TeamContext } from '../context';
import { deleteOrg, deleteOrgMember, getOrgList } from '@/web/support/user/team/org/api';
import IconButton from './IconButton';
import { defaultOrgForm, type OrgFormType } from './OrgInfoModal';
import dynamic from 'next/dynamic';
import MyBox from '@fastgpt/web/components/common/MyBox';
import Path from '@/components/common/folder/Path';
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant';
import { useSystemStore } from '@/web/common/system/useSystemStore';
const OrgInfoModal = dynamic(() => import('./OrgInfoModal'));
const OrgMemberManageModal = dynamic(() => import('./OrgMemberManageModal'));
const OrgMoveModal = dynamic(() => import('./OrgMoveModal'));
function ActionButton({
icon,
text,
onClick
}: {
icon: IconNameType;
text: string;
onClick: () => void;
}) {
return (
<HStack
gap={'8px'}
w="100%"
transition={'background 0.1s'}
cursor={'pointer'}
p="4px"
rounded={'sm'}
_hover={{
bg: 'myGray.05',
color: 'primary.600'
}}
onClick={onClick}
>
<MyIcon name={icon} w="1rem" h="1rem" />
<Box fontSize={'sm'}>{text}</Box>
</HStack>
);
}
function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
const { t } = useTranslation();
const { userInfo, isTeamAdmin } = useUserStore();
const { members, MemberScrollData } = useContextSelector(TeamContext, (v) => v);
const { feConfigs } = useSystemStore();
const isSyncMember = feConfigs.register_method?.includes('sync');
const [parentPath, setParentPath] = useState('');
const {
data: orgs = [],
loading: isLoadingOrgs,
refresh: refetchOrgs
} = useRequest2(getOrgList, {
manual: false,
refreshDeps: [userInfo?.team?.teamId]
});
const currentOrgs = useMemo(() => {
if (orgs.length === 0) return [];
// Auto select the first org(root org is team)
if (parentPath === '') {
setParentPath(getOrgChildrenPath(orgs[0]));
return [];
}
return orgs
.filter((org) => org.path === parentPath)
.map((item) => {
return {
...item,
// Member + org
count:
item.members.length + orgs.filter((org) => org.path === getOrgChildrenPath(item)).length
};
});
}, [orgs, parentPath]);
const currentOrg = useMemo(() => {
const splitPath = parentPath.split('/');
const currentOrgId = splitPath[splitPath.length - 1];
if (!currentOrgId) return;
return orgs.find((org) => org.pathId === currentOrgId);
}, [orgs, parentPath]);
const paths = useMemo(() => {
const splitPath = parentPath.split('/').filter(Boolean);
return splitPath
.map((id) => {
const org = orgs.find((org) => org.pathId === id)!;
if (org.path === '') return;
return {
parentId: getOrgChildrenPath(org),
parentName: org.name
};
})
.filter(Boolean) as ParentTreePathItemType[];
}, [parentPath, orgs]);
const [editOrg, setEditOrg] = useState<OrgFormType>();
const [manageMemberOrg, setManageMemberOrg] = useState<OrgType>();
const [movingOrg, setMovingOrg] = useState<OrgType>();
// Delete org
const { ConfirmModal: ConfirmDeleteOrgModal, openConfirm: openDeleteOrgModal } = useConfirm({
type: 'delete',
content: t('account_team:confirm_delete_org')
});
const deleteOrgHandler = (orgId: string) => openDeleteOrgModal(() => deleteOrgReq(orgId))();
const { runAsync: deleteOrgReq } = useRequest2(deleteOrg, {
onSuccess: () => {
refetchOrgs();
}
});
// Delete member
const { ConfirmModal: ConfirmDeleteMember, openConfirm: openDeleteMemberModal } = useConfirm({
type: 'delete',
content: t('account_team:confirm_delete_member')
});
const { runAsync: deleteMemberReq } = useRequest2(deleteOrgMember, {
onSuccess: () => {
refetchOrgs();
}
});
return (
<>
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
{Tabs}
</Flex>
<MyBox
flex={'1 0 0'}
h={0}
display={'flex'}
flexDirection={'column'}
isLoading={isLoadingOrgs}
>
<Box mb={3}>
<Path paths={paths} rootName={userInfo?.team?.teamName} onClick={setParentPath} />
</Box>
<Flex flex={'1 0 0'} h={0} w={'100%'} gap={'4'}>
<MemberScrollData h={'100%'} fontSize={'sm'} flexGrow={1}>
{/* Table */}
<TableContainer>
<Table>
<Thead>
<Tr bg={'white !important'}>
<Th bg="myGray.100" borderLeftRadius="6px">
{t('common:Name')}
</Th>
{!isSyncMember && (
<Th bg="myGray.100" borderRightRadius="6px">
{t('common:common.Action')}
</Th>
)}
</Tr>
</Thead>
<Tbody>
{currentOrgs.map((org) => (
<Tr key={org._id} overflow={'unset'}>
<Td>
<HStack
cursor={'pointer'}
onClick={() => setParentPath(getOrgChildrenPath(org))}
>
<MemberTag name={org.name} avatar={org.avatar} />
<Tag size="sm">{org.count}</Tag>
<MyIcon
name="core/chat/chevronRight"
w={'1rem'}
h={'1rem'}
color={'myGray.500'}
/>
</HStack>
</Td>
{isTeamAdmin && !isSyncMember && (
<Td w={'6rem'}>
<MyMenu
trigger="hover"
Button={<IconButton name="more" />}
menuList={[
{
children: [
{
icon: 'edit',
label: t('account_team:edit_info'),
onClick: () => setEditOrg(org)
},
{
icon: 'common/file/move',
label: t('common:Move'),
onClick: () => setMovingOrg(org)
},
{
icon: 'delete',
label: t('account_team:delete'),
type: 'danger',
onClick: () => deleteOrgHandler(org._id)
}
]
}
]}
/>
</Td>
)}
</Tr>
))}
{currentOrg?.members.map((member) => {
const memberInfo = members.find((m) => m.tmbId === member.tmbId);
if (!memberInfo) return null;
return (
<Tr key={member.tmbId}>
<Td>
<MemberTag name={memberInfo.memberName} avatar={memberInfo.avatar} />
</Td>
<Td w={'6rem'}>
{isTeamAdmin && !isSyncMember && (
<MyMenu
trigger={'hover'}
Button={<IconButton name="more" />}
menuList={[
{
children: [
{
icon: 'delete',
label: t('account_team:delete'),
type: 'danger',
onClick: () =>
openDeleteMemberModal(() =>
deleteMemberReq(currentOrg._id, member.tmbId)
)()
}
]
}
]}
/>
)}
</Td>
</Tr>
);
})}
</Tbody>
</Table>
</TableContainer>
</MemberScrollData>
{/* Slider */}
{!isSyncMember && (
<VStack w={'180px'} alignItems={'start'}>
<HStack gap={'6px'}>
<Avatar src={currentOrg?.avatar} w={'1rem'} h={'1rem'} rounded={'xs'} />
<Box fontWeight={500} color={'myGray.900'}>
{currentOrg?.name}
</Box>
{currentOrg?.path !== '' && (
<IconButton name="edit" onClick={() => setEditOrg(currentOrg)} />
)}
</HStack>
<Box fontSize={'xs'}>{currentOrg?.description || t('common:common.no_intro')}</Box>
<Divider my={'20px'} />
<Box fontWeight={500} fontSize="sm" color="myGray.900">
{t('common:common.Action')}
</Box>
{currentOrg && isTeamAdmin && (
<VStack gap="13px" w="100%">
<ActionButton
icon="common/add2"
text={t('account_team:create_sub_org')}
onClick={() => {
setEditOrg({
...defaultOrgForm,
parentId: currentOrg?._id
});
}}
/>
<ActionButton
icon="common/administrator"
text={t('account_team:manage_member')}
onClick={() => setManageMemberOrg(currentOrg)}
/>
{currentOrg?.path !== '' && (
<>
<ActionButton
icon="common/file/move"
text={t('account_team:move_org')}
onClick={() => setMovingOrg(currentOrg)}
/>
<ActionButton
icon="delete"
text={t('account_team:delete_org')}
onClick={() => deleteOrgHandler(currentOrg._id)}
/>
</>
)}
</VStack>
)}
</VStack>
)}
</Flex>
</MyBox>
{!!editOrg && (
<OrgInfoModal
editOrg={editOrg}
onClose={() => setEditOrg(undefined)}
onSuccess={refetchOrgs}
/>
)}
{!!movingOrg && (
<OrgMoveModal
orgs={orgs}
movingOrg={movingOrg}
onClose={() => setMovingOrg(undefined)}
onSuccess={refetchOrgs}
/>
)}
{!!manageMemberOrg && (
<OrgMemberManageModal
currentOrg={manageMemberOrg}
refetchOrgs={refetchOrgs}
onClose={() => setManageMemberOrg(undefined)}
/>
)}
<ConfirmDeleteOrgModal />
<ConfirmDeleteMember />
</>
);
}
export default OrgTable;