Files
SMATEC-FRONTEND/src/pages/Manager/User/index.tsx

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;