415 lines
13 KiB
TypeScript
415 lines
13 KiB
TypeScript
import TooltipIconFontButton from '@/components/shared/TooltipIconFontButton';
|
|
import TreeGroup from '@/components/shared/TreeGroup';
|
|
import { DEFAULT_PAGE_SIZE } from '@/constants';
|
|
import {
|
|
ROUTE_MANAGER_USERS,
|
|
ROUTE_MANAGER_USERS_PERMISSIONS,
|
|
} from '@/constants/routes';
|
|
import {
|
|
apiDeleteUser,
|
|
apiQueryUsers,
|
|
apiQueryUsersByGroup,
|
|
} from '@/services/master/UserController';
|
|
import { DeleteOutlined } from '@ant-design/icons';
|
|
import {
|
|
ActionType,
|
|
FooterToolbar,
|
|
ProCard,
|
|
ProColumns,
|
|
ProTable,
|
|
} from '@ant-design/pro-components';
|
|
import { FormattedMessage, history, useIntl } from '@umijs/max';
|
|
import { Button, Grid, Popconfirm, Space, theme } from 'antd';
|
|
import message from 'antd/es/message';
|
|
import Paragraph from 'antd/lib/typography/Paragraph';
|
|
import { useRef, useState } from 'react';
|
|
import CreateUser from './components/CreateUser';
|
|
import ResetPassword from './components/ResetPassword';
|
|
type ResetUserPaswordProps = {
|
|
user: MasterModel.UserResponse | null;
|
|
isOpen: boolean;
|
|
};
|
|
const ManagerUserPage = () => {
|
|
const { useBreakpoint } = Grid;
|
|
const intl = useIntl();
|
|
const screens = useBreakpoint();
|
|
const { token } = theme.useToken();
|
|
const actionRef = useRef<ActionType | null>(null);
|
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
const [messageApi, contextHolder] = message.useMessage();
|
|
const [selectedRowsState, setSelectedRowsState] = useState<
|
|
MasterModel.UserResponse[]
|
|
>([]);
|
|
|
|
const [groupCheckedKeys, setGroupCheckedKeys] = useState<
|
|
string | string[] | null
|
|
>(null);
|
|
|
|
const [resetPasswordUser, setResetPasswordUser] =
|
|
useState<ResetUserPaswordProps>({
|
|
user: null,
|
|
isOpen: false,
|
|
});
|
|
|
|
const handleClickAssign = (user: MasterModel.UserResponse) => {
|
|
const path = `${ROUTE_MANAGER_USERS}/${user.id}/${ROUTE_MANAGER_USERS_PERMISSIONS}`;
|
|
history.push(path);
|
|
};
|
|
|
|
const handleClickResetPassword = (user: MasterModel.UserResponse) => {
|
|
setResetPasswordUser({ user: user, isOpen: true });
|
|
};
|
|
|
|
const columns: ProColumns<MasterModel.UserResponse>[] = [
|
|
{
|
|
key: 'email',
|
|
title: (
|
|
<FormattedMessage id="master.users.email" defaultMessage="Email" />
|
|
),
|
|
tip: intl.formatMessage({
|
|
id: 'master.users.email.tip',
|
|
defaultMessage: 'The email is the unique key',
|
|
}),
|
|
dataIndex: 'email',
|
|
render: (_, record) => (
|
|
<Paragraph
|
|
style={{
|
|
marginBottom: 0,
|
|
verticalAlign: 'middle',
|
|
display: 'inline-block',
|
|
color: token.colorText,
|
|
}}
|
|
copyable
|
|
>
|
|
{record?.email}
|
|
</Paragraph>
|
|
),
|
|
},
|
|
|
|
{
|
|
key: 'phone_number',
|
|
title: (
|
|
<FormattedMessage
|
|
id="master.users.phone_number"
|
|
defaultMessage="Phone number"
|
|
/>
|
|
),
|
|
tip: intl.formatMessage({
|
|
id: 'master.users.phone_number.tip',
|
|
defaultMessage: 'The phone number is the unique key',
|
|
}),
|
|
responsive: ['lg', 'md'],
|
|
dataIndex: ['metadata', 'phone_number'],
|
|
render: (_, record) =>
|
|
record?.metadata?.phone_number ? (
|
|
<Paragraph
|
|
style={{
|
|
marginBottom: 0,
|
|
verticalAlign: 'middle',
|
|
display: 'inline-block',
|
|
color: token.colorText,
|
|
}}
|
|
copyable
|
|
>
|
|
{record?.metadata?.phone_number}
|
|
</Paragraph>
|
|
) : (
|
|
'-'
|
|
),
|
|
},
|
|
|
|
{
|
|
key: 'user_type',
|
|
hideInSearch: true,
|
|
title: <FormattedMessage id="master.users.role" defaultMessage="Role" />,
|
|
tip: intl.formatMessage({
|
|
id: 'master.users.role.tip',
|
|
defaultMessage: 'The role is the unique key',
|
|
}),
|
|
dataIndex: ['metadata', 'user_type'],
|
|
render: (_, record) => record?.metadata?.user_type || '...',
|
|
},
|
|
|
|
{
|
|
title: (
|
|
<FormattedMessage id="common.actions" defaultMessage="Operating" />
|
|
),
|
|
hideInSearch: true,
|
|
render: (_, user) => {
|
|
return (
|
|
<Space>
|
|
<TooltipIconFontButton
|
|
shape="default"
|
|
size="small"
|
|
iconFontName="icon-assign"
|
|
tooltip={intl.formatMessage({
|
|
id: 'master.users.change_role.title',
|
|
defaultMessage: 'Set Permissions',
|
|
})}
|
|
onClick={() => handleClickAssign(user)}
|
|
/>
|
|
<TooltipIconFontButton
|
|
shape="default"
|
|
size="small"
|
|
iconFontName="icon-reset-password"
|
|
tooltip={intl.formatMessage({
|
|
id: 'master.users.resetPassword.title',
|
|
defaultMessage: 'Reset Password',
|
|
})}
|
|
onClick={() => handleClickResetPassword(user)}
|
|
/>
|
|
</Space>
|
|
);
|
|
},
|
|
},
|
|
];
|
|
|
|
const handleRemove = async (selectedRows: MasterModel.UserResponse[]) => {
|
|
const key = 'remove_user';
|
|
if (!selectedRows) return true;
|
|
|
|
try {
|
|
messageApi.open({
|
|
type: 'loading',
|
|
content: intl.formatMessage({
|
|
id: 'common.deleting',
|
|
defaultMessage: 'deleting...',
|
|
}),
|
|
duration: 0,
|
|
key,
|
|
});
|
|
const allDelete = selectedRows.map(
|
|
async (row: MasterModel.UserResponse) => {
|
|
await apiDeleteUser(row?.id || '');
|
|
},
|
|
);
|
|
await Promise.all(allDelete);
|
|
messageApi.open({
|
|
type: 'success',
|
|
content: intl.formatMessage({
|
|
id: 'master.users.delete.success',
|
|
defaultMessage: 'Deleted successfully and will refresh soon',
|
|
}),
|
|
key,
|
|
});
|
|
return true;
|
|
} catch (error) {
|
|
messageApi.open({
|
|
type: 'error',
|
|
content: intl.formatMessage({
|
|
id: 'master.users.delete.fail',
|
|
defaultMessage: 'Delete failed, please try again!',
|
|
}),
|
|
key,
|
|
});
|
|
return false;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{contextHolder}
|
|
{resetPasswordUser.user && (
|
|
<ResetPassword
|
|
message={messageApi}
|
|
isOpen={resetPasswordUser.isOpen}
|
|
user={resetPasswordUser.user}
|
|
setIsOpen={(isOpen) =>
|
|
setResetPasswordUser((prev) => ({ ...prev, isOpen }))
|
|
}
|
|
onSuccess={(isSuccess) => {
|
|
if (isSuccess) actionRef.current?.reload();
|
|
}}
|
|
/>
|
|
)}
|
|
<ProCard split={screens.md ? 'vertical' : 'horizontal'}>
|
|
<ProCard colSpan={{ xs: 24, sm: 24, md: 6, lg: 6, xl: 6 }}>
|
|
<TreeGroup
|
|
disable={isLoading}
|
|
multiple={true}
|
|
groupIds={groupCheckedKeys}
|
|
onSelected={(value: string | string[] | null) => {
|
|
setGroupCheckedKeys(value);
|
|
if (actionRef.current) {
|
|
actionRef.current.reload();
|
|
}
|
|
}}
|
|
/>
|
|
</ProCard>
|
|
<ProCard colSpan={{ xs: 24, sm: 24, md: 18, lg: 18, xl: 18 }}>
|
|
<ProTable<MasterModel.UserResponse>
|
|
columns={columns}
|
|
tableLayout="auto"
|
|
actionRef={actionRef}
|
|
rowKey="id"
|
|
search={{
|
|
layout: 'vertical',
|
|
defaultCollapsed: false,
|
|
}}
|
|
dateFormatter="string"
|
|
rowSelection={{
|
|
selectedRowKeys: selectedRowsState.map((row) => row.id!),
|
|
onChange: (
|
|
_: React.Key[],
|
|
selectedRows: MasterModel.UserResponse[],
|
|
) => {
|
|
setSelectedRowsState(selectedRows);
|
|
},
|
|
}}
|
|
pagination={{
|
|
defaultPageSize: DEFAULT_PAGE_SIZE * 2,
|
|
showSizeChanger: true,
|
|
pageSizeOptions: ['10', '15', '20'],
|
|
showTotal: (total, range) =>
|
|
`${range[0]}-${range[1]}
|
|
${intl.formatMessage({
|
|
id: 'common.paginations.of',
|
|
defaultMessage: 'of',
|
|
})}
|
|
${total} ${intl.formatMessage({
|
|
id: 'master.users.table.pagination',
|
|
defaultMessage: 'users',
|
|
})}`,
|
|
}}
|
|
request={async (params = {}) => {
|
|
const { current = 1, pageSize, email, phone_number } = params;
|
|
const size = pageSize || DEFAULT_PAGE_SIZE * 2;
|
|
const offset = current === 1 ? 0 : (current - 1) * size;
|
|
setIsLoading(true);
|
|
// If groups are checked, use queryUsersByGroup
|
|
try {
|
|
if (groupCheckedKeys && groupCheckedKeys.length > 0) {
|
|
// Ensure groupCheckedKeys is an array
|
|
const groupIdsArray = Array.isArray(groupCheckedKeys)
|
|
? groupCheckedKeys.join(',')
|
|
: groupCheckedKeys;
|
|
|
|
const userByGroupResponses = await apiQueryUsersByGroup(
|
|
groupIdsArray,
|
|
);
|
|
let users = userByGroupResponses.users || [];
|
|
// Apply filters
|
|
if (email) {
|
|
users = users.filter((user: MasterModel.UserResponse) =>
|
|
user.email?.includes(email),
|
|
);
|
|
}
|
|
if (phone_number) {
|
|
users = users.filter((user: MasterModel.UserResponse) =>
|
|
user.metadata?.phone_number?.includes(phone_number),
|
|
);
|
|
}
|
|
|
|
const total = users.length;
|
|
const paginatedUsers = users.slice(offset, offset + size);
|
|
setIsLoading(false);
|
|
return {
|
|
data: paginatedUsers,
|
|
success: true,
|
|
total: total,
|
|
};
|
|
} else {
|
|
// Use regular queryUsers API
|
|
const metadata: Partial<MasterModel.UserMetadata> = {};
|
|
if (phone_number) metadata.phone_number = phone_number;
|
|
|
|
const query: MasterModel.SearchUserPaginationBody = {
|
|
offset: offset,
|
|
limit: size,
|
|
order: 'name',
|
|
dir: 'asc',
|
|
};
|
|
if (email) query.email = email;
|
|
if (Object.keys(metadata).length > 0)
|
|
query.metadata = metadata;
|
|
|
|
const response = await apiQueryUsers(query);
|
|
setIsLoading(false);
|
|
return {
|
|
data: response.users,
|
|
success: true,
|
|
total: response.total,
|
|
};
|
|
}
|
|
} catch (error) {
|
|
setIsLoading(false);
|
|
return {
|
|
data: [],
|
|
success: false,
|
|
total: 0,
|
|
};
|
|
}
|
|
}}
|
|
options={{
|
|
search: false,
|
|
setting: false,
|
|
density: false,
|
|
reload: true,
|
|
}}
|
|
toolBarRender={() => [
|
|
<CreateUser
|
|
message={messageApi}
|
|
onSuccess={(isSuccess) => {
|
|
if (isSuccess) {
|
|
actionRef.current?.reload();
|
|
}
|
|
}}
|
|
key="create-user"
|
|
/>,
|
|
]}
|
|
/>
|
|
</ProCard>
|
|
</ProCard>
|
|
{selectedRowsState?.length > 0 && (
|
|
<FooterToolbar
|
|
extra={
|
|
<div>
|
|
<FormattedMessage
|
|
id="master.footer.chosen"
|
|
defaultMessage="Chosen"
|
|
/>{' '}
|
|
<a
|
|
style={{
|
|
fontWeight: 600,
|
|
}}
|
|
>
|
|
{selectedRowsState.length}
|
|
</a>{' '}
|
|
<FormattedMessage
|
|
id="master.users.table.pagination"
|
|
defaultMessage="users"
|
|
/>
|
|
</div>
|
|
}
|
|
>
|
|
<Popconfirm
|
|
title={intl.formatMessage({
|
|
id: 'master.users.delete.title',
|
|
defaultMessage: 'Are you sure to delete this selected items',
|
|
})}
|
|
okText={intl.formatMessage({
|
|
id: 'common.sure',
|
|
defaultMessage: 'Sure',
|
|
})}
|
|
onConfirm={async () => {
|
|
const success = await handleRemove(selectedRowsState);
|
|
if (success) {
|
|
setSelectedRowsState([]);
|
|
if (actionRef.current) {
|
|
actionRef.current.reload();
|
|
}
|
|
}
|
|
}}
|
|
>
|
|
<Button icon={<DeleteOutlined />} type="primary" danger>
|
|
<FormattedMessage id="common.delete" defaultMessage="Delete" />
|
|
</Button>
|
|
</Popconfirm>
|
|
</FooterToolbar>
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default ManagerUserPage;
|