feat(project): base smatec's frontend

This commit is contained in:
Tran Anh Tuan
2026-01-21 11:48:57 +07:00
commit 5c2a909bed
138 changed files with 43666 additions and 0 deletions

View File

@@ -0,0 +1,258 @@
import TreeGroup from '@/components/shared/TreeGroup';
import { DEFAULT_PAGE_SIZE } from '@/constants';
import {
apiQueryUsers,
apiQueryUsersByGroup,
} from '@/services/master/UserController';
import {
ActionType,
ProCard,
ProColumns,
ProTable,
} from '@ant-design/pro-components';
import { FormattedMessage, useIntl } from '@umijs/max';
import { Grid } 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';
const ManagerUserPage = () => {
const { useBreakpoint } = Grid;
const intl = useIntl();
const screens = useBreakpoint();
const actionRef = useRef<ActionType | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [messageApi, contextHolder] = message.useMessage();
const [selectedRowsState, setSelectedRowsState] = useState<
MasterModel.ProfileResponse[]
>([]);
const [groupCheckedKeys, setGroupCheckedKeys] = useState<
string | string[] | null
>(null);
const columns: ProColumns<MasterModel.ProfileResponse>[] = [
{
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',
}}
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',
}}
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: () => {
return (
<>
{/* <PermissionButton
user={record}
title={intl.formatMessage({
id: 'master.users.assign',
defaultMessage: 'Assgin',
})}
/> */}
</>
);
},
},
];
return (
<>
{contextHolder}
<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.ProfileResponse>
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.ProfileResponse[],
) => {
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
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.ProfileResponse) =>
user.email?.includes(email),
);
}
if (phone_number) {
users = users.filter((user: MasterModel.ProfileResponse) =>
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.ProfileMetadata> = {};
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,
};
}
}}
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>
</>
);
};
export default ManagerUserPage;