Skip to content

Commit

Permalink
optimize: Operation buttons on the user management interface
Browse files Browse the repository at this point in the history
Signed-off-by: 孙林耀 <[email protected]>
  • Loading branch information
MicroOps-cn committed Sep 23, 2024
1 parent 25d2aa1 commit 6ec51d5
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 84 deletions.
127 changes: 127 additions & 0 deletions public/src/components/ButtonGroup/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Dropdown, Button as AntdButton, DropdownProps, Space } from 'antd';
import type { ButtonProps as AntdButtonProps } from 'antd';
import { message } from 'antd';
import { ButtonType } from 'antd/lib/button';
import { MenuItemType } from 'antd/lib/menu/hooks/useItems';
import { isBoolean } from 'lodash';
import { MenuInfo } from 'rc-menu/lib/interface';
import { ReactNode } from 'react';
import { useState } from 'react';

import { DownOutlined } from '@ant-design/icons';

interface ButtonProps extends AntdButtonProps {
success?: React.ReactElement;
failed?: React.ReactElement;
onClick: (event: React.MouseEvent<HTMLElement, MouseEvent>) => Promise<void | boolean>;
}

export const Button: React.FC<ButtonProps> = ({ success, failed, onClick, ...props }) => {
const [loading, setLoading] = useState<boolean>(false);
const [status, setStatus] = useState<0 | 1 | 2>(0);
return (
<AntdButton
{...props}
loading={loading}
onClick={async (e) => {
if (onClick) {
try {
setLoading(true);
const ok = await onClick(e);
if (isBoolean(ok) && !ok) {
setStatus(2);
} else {
setStatus(1);
}
} catch (error) {
message.error(`${error}`, 3);
setStatus(2);
} finally {
setLoading(false);
}
}
}}
>
{status === 1 && success ? success : status === 2 && failed ? failed : props.children}
</AntdButton>
);
};

interface ButtonItemType extends MenuItemType {
hidden?: boolean;
failed?: React.ReactElement;
success?: React.ReactElement;
type?: ButtonType;
onClick?: (info: Omit<MenuInfo, 'item' | 'keyPath'>) => void;
}

interface ButtonGroupProps extends DropdownProps {
maxItems?: number;
items: ButtonItemType[];
moreLabel?: ReactNode;
}

const GroupButtonItem: React.FC<ButtonItemType> = ({
success,
failed,
key,
style,
type = 'link',
onClick,
label,
}) => {
return (
<Button
success={success}
failed={failed}
key={key}
style={{
padding: '4px 0px',
...style,
}}
type={type}
onClick={async (e) => {
return onClick?.({
key: key.toString(),
domEvent: e,
});
}}
>
{label}
</Button>
);
};

export const ButtonGroup: React.FC<ButtonGroupProps> = ({
maxItems,
items,
moreLabel = 'More',
}) => {
const visibleItems = items.filter((item) => item && !item.hidden);
if (maxItems === undefined || maxItems >= visibleItems.length) {
const buttons = visibleItems.map(GroupButtonItem);
if (maxItems === undefined) {
return <AntdButton.Group>{buttons}</AntdButton.Group>;
}
return <Space>{buttons}</Space>;
}
const dropdownItems = visibleItems.slice(maxItems - 1).map((item) => {
return {
...item,
label: <div style={{ padding: '0px 15px' }}>{item.label}</div>,
};
});
return (
<Space>
{visibleItems.slice(0, maxItems - 1).map(GroupButtonItem)}
<Dropdown menu={{ items: dropdownItems }} trigger={['click']}>
<a onClick={(e) => e.preventDefault()} style={{ gap: 3, display: 'inline-flex' }}>
{moreLabel}
<DownOutlined />
</a>
</Dropdown>
</Space>
);
};

export default ButtonGroup;
48 changes: 0 additions & 48 deletions public/src/pages/User/components/MultiStatusButton.tsx

This file was deleted.

5 changes: 5 additions & 0 deletions public/src/pages/User/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
}
}
}
.UserDetails {
:global .ant-descriptions-extra > .ant-space {
flex-flow: row-reverse;
}
}
:global {
.ant-modal:has(.ant-pro-steps-form-step-active > .grant-view) {
top: 30px;
Expand Down
154 changes: 118 additions & 36 deletions public/src/pages/User/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import moment from 'moment';
import React, { useState, useRef, useEffect } from 'react';
import { useIntl } from 'umi';

import ButtonGroup from '@/components/ButtonGroup';
import { UserStatus } from '@/services/idas/enums';
import { getSessions as getUserSessions, deleteSession } from '@/services/idas/sessions';
import {
Expand Down Expand Up @@ -33,7 +34,7 @@ import ProTable from '@ant-design/pro-table';
import type { FormValueType } from './components/CreateOrUpdateForm';
import CreateOrUpdateForm from './components/CreateOrUpdateForm';
import GrantView from './components/GrantView';
import MultiStatusButton from './components/MultiStatusButton';
import styles from './index.less';

/**
* @en-US Add node
Expand Down Expand Up @@ -353,42 +354,111 @@ const UserList: React.FC = () => {
dataIndex: 'option',
valueType: 'option',
render: (_, record) => {
const options: JSX.Element[] = [
<a
style={{ flex: 'unset' }}
key="config"
onClick={() => {
handleModalVisible(true);
setCurrentRow(record);
}}
>
{intl.t('button.edit', 'Edit')}
</a>,
<MultiStatusButton
type="link"
key="activate"
style={{ flex: 'unset' }}
success={<CheckOutlined color="green" />}
onClick={async () => {
if (!record.email) {
throw new Error(intl.t('activate.no-email', ' The user has no email.'));
}
const resp = await sendActivateMail({
userId: record.id,
});
if (resp.success) {
message.success(intl.t('activate.succcess', 'Email sent successfully.'));
} else {
throw new Error('Email sent failed.');
}
}}
hidden={record.status !== UserStatus.user_inactive}
>
{intl.t('button.activate', 'Activate')}
</MultiStatusButton>,
return [
<ButtonGroup
maxItems={2}
moreLabel={intl.t('button.more', 'More')}
items={[
{
key: 'edit',
label: intl.t('button.edit', 'Edit'),
style: { flex: 'unset' },
onClick: async () => {
handleModalVisible(true);
setCurrentRow(record);
},
},
{
key: 'activate',
label: intl.t('button.activate', 'Activate'),
hidden: record.status !== UserStatus.user_inactive,
success: <CheckOutlined color="green" />,
style: { flex: 'unset' },
onClick: async () => {
if (!record.email) {
throw new Error(intl.t('activate.no-email', ' The user has no email.'));
}
const resp = await sendActivateMail({
userId: record.id,
});
if (resp.success) {
message.success(intl.t('activate.succcess', 'Email sent successfully.'));
} else {
throw new Error(intl.t('activate.failed', 'Email sent failed.'));
}
},
},
{
key: 'delete',
label: intl.t('button.delete', 'Delete'),
style: { flex: 'unset' },
onClick: () => {
Modal.confirm({
title: intl.t(
'deleteConfirm',
'Are you sure you want to delete the following users? ',
),
icon: <ExclamationCircleOutlined />,
async onOk() {
await handleRemove([record]);
},
content: (
<List<API.UserInfo>
dataSource={[record]}
rowKey={'id'}
renderItem={(item) => (
<List.Item>
{item.username}
{item.fullName
? `(${item.fullName})`
: item.email
? `(${item.email})`
: ''}
</List.Item>
)}
/>
),
});
},
},
{
key: 'disable',
label: intl.t('button.disable', 'Disable'),
hidden: record.status === UserStatus.disabled,
style: { flex: 'unset' },
onClick: () => {
Modal.confirm({
title: intl.t(
'disableConfirm',
'Are you sure you want to disable the following users?',
),
icon: <ExclamationCircleOutlined />,
async onOk() {
await handleDisable([record]);
actionRef.current?.reloadAndRest?.();
},
content: (
<List<API.UserInfo>
dataSource={[record].filter((user) => user.status !== UserStatus.disabled)}
rowKey={'id'}
renderItem={(item) => (
<List.Item>
{item.username}
{item.fullName
? `(${item.fullName})`
: item.email
? `(${item.email})`
: ''}
</List.Item>
)}
/>
),
});
},
},
]}
/>,
];

return options;
},
},
];
Expand All @@ -399,6 +469,7 @@ const UserList: React.FC = () => {
actionRef={actionRef}
rowKey="id"
search={false}
tableAlertRender={false}
toolbar={{
search: {
onSearch: (kws) => {
Expand Down Expand Up @@ -633,6 +704,7 @@ const UserList: React.FC = () => {
params={{
id: currentRow?.id,
}}
className={styles.UserDetails}
extra={
<>
<a
Expand Down Expand Up @@ -664,6 +736,16 @@ const UserList: React.FC = () => {
>
{intl.t('button.save', 'Save')}
</a>
<a
key="cancel"
onClick={async () => {
setGranting(false);
}}
style={{ flex: 'unset' }}
hidden={!granting}
>
{intl.t('button.cancel', 'Cancel')}
</a>
</>
}
columns={columns as ProDescriptionsItemProps<API.UserInfo>[]}
Expand Down

0 comments on commit 6ec51d5

Please sign in to comment.