Files
MemoryBear/web/src/views/UserManagement/index.tsx
2026-02-25 15:47:13 +08:00

167 lines
4.6 KiB
TypeScript

/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:51:08
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-25 15:44:54
*/
/**
* User Management Page
* Manages users with create, enable/disable, and password reset capabilities
*/
import React, { useRef } from 'react';
import { Button, Space, App } from 'antd';
import { useTranslation } from 'react-i18next';
import type { ColumnsType } from 'antd/es/table';
import CreateModal from './components/CreateModal';
import type { CreateModalRef, User, ResetPasswordModalRef } from './types'
import Table, { type TableRef } from '@/components/Table'
import StatusTag from '@/components/StatusTag'
import { deleteUser, enableUser, getUserListUrl } from '@/api/user'
import ResetPasswordModal from './components/ResetPasswordModal'
import { formatDateTime } from '@/utils/format';
const UserManagement: React.FC = () => {
const { t } = useTranslation();
const { message, modal } = App.useApp();
const userFormRef = useRef<CreateModalRef>(null);
const resetPasswordModalRef = useRef<ResetPasswordModalRef>(null);
const tableRef = useRef<TableRef>(null);
/** Open create user modal */
const handleCreate = () => {
userFormRef.current?.handleOpen();
}
/** Reset user password */
const handleResetPassword = (user: User) => {
resetPasswordModalRef.current?.handleOpen(user);
};
/** Refresh table data */
const refreshTable = () => {
tableRef.current?.loadData()
}
/** Enable/disable user */
const handleChangeStatus = async (record: User) => {
modal.confirm({
title: t(`user.${record.is_active ? 'disabled' : 'enabled'}Confirm`),
okText: t('common.confirm'),
cancelText: t('common.cancel'),
okType: 'danger',
onOk: () => {
const res = record.is_active ? deleteUser(record.id) : enableUser(record.id);
res.then(() => {
message.success(t(`user.${record.is_active ? 'disabled' : 'enabled'}ConfirmSuccess`));
refreshTable();
})
},
})
};
/** Table column configuration */
const columns: ColumnsType = [
{
title: t('user.userId'),
dataIndex: 'id',
key: 'id',
fixed: 'left',
},
{
title: <>{t('user.username')}<div className="rb:text-[#5B6167] rb:text-[12px] rb:font-medium">({t(`user.subUsername`)})</div></>,
dataIndex: 'email',
key: 'email',
},
{
title: t('user.displayName'),
dataIndex: 'username',
key: 'username',
},
{
title: t('user.role'),
dataIndex: 'is_superuser',
key: 'is_superuser',
render: (isSuperuser: boolean) => isSuperuser ? t('user.superuser') : t('user.normalUser'),
},
{
title: t('user.status'),
dataIndex: 'is_active',
key: 'is_active',
render: (isActive: boolean) => (
<StatusTag
text={isActive ? t('user.enabled') : t('user.disabled')}
status={isActive ? 'success' : 'error'}
/>
),
},
{
title: t('user.createTime'),
dataIndex: 'created_at',
key: 'created_at',
render: (createdAt: string) => formatDateTime(createdAt, 'YYYY-MM-DD HH:mm:ss'),
},
{
title: t('user.lastLoginTime'),
dataIndex: 'last_login_at',
key: 'last_login_at',
render: (lastLoginAt: string) => lastLoginAt ? formatDateTime(lastLoginAt, 'YYYY-MM-DD HH:mm:ss') : '-',
},
{
title: t('common.operation'),
key: 'action',
fixed: 'right',
render: (_, record) => (
<Space size="large">
{record.is_active &&
<Button
type="link"
onClick={() => handleResetPassword(record as User)}
>
{t('user.resetPassword')}
</Button>
}
<Button
type="link"
onClick={() => handleChangeStatus(record as User)}
>
{t(`user.${record.is_active ? 'disabledOpera' : 'enabledOpera'}`)}
</Button>
</Space>
),
},
];
return (
<div className="rb:h-[calc(100vh-80px)] rb:overflow-hidden">
<div className="rb:flex rb:justify-end rb:mb-3">
<Button type="primary" onClick={handleCreate}>
{t('user.createUser')}
</Button>
</div>
<Table
ref={tableRef}
apiUrl={getUserListUrl}
apiParams={{
include_inactive: true,
}}
columns={columns}
rowKey="id"
isScroll={true}
/>
<CreateModal
ref={userFormRef}
refreshTable={refreshTable}
/>
<ResetPasswordModal
ref={resetPasswordModalRef}
/>
</div>
);
};
export default UserManagement;