mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-30 08:59:34 +00:00
feat(clients): tidier bulk action toolbar
When at least one client is selected, the toolbar now collapses to a
small selection indicator plus the three most-used actions instead of
spreading six count-suffixed buttons across the row:
- Replaces every per-button "(N)" with a single closable "{N} selected"
tag on the left — one click on its × clears the selection.
- Hides "+ Add Clients" while a selection is active (focus mode).
- Keeps Attach, Detach, and Delete as visible buttons; Delete is pushed
to the right with auto margin so it doesn't sit flush against the
non-destructive actions.
- Folds Adjust, Group, and Sub links into the existing "more"
dropdown, which is now context-aware: selection-scoped overflow when
rows are picked, global actions (Add Bulk / Reset all / Del depleted)
otherwise.
On mobile the new buttons collapse to icon-only the same way as the
rest of the toolbar.
This commit is contained in:
@@ -783,28 +783,25 @@ export default function ClientsPage() {
|
||||
hoverable
|
||||
title={
|
||||
<div className="card-toolbar">
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={onAdd}>
|
||||
{!isMobile && t('pages.clients.addClients')}
|
||||
</Button>
|
||||
{selectedRowKeys.length > 0 && (
|
||||
{selectedRowKeys.length === 0 ? (
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={onAdd}>
|
||||
{!isMobile && t('pages.clients.addClients')}
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button icon={<ClockCircleOutlined />} onClick={() => setBulkAdjustOpen(true)}>
|
||||
{t('pages.clients.adjustSelected', { count: selectedRowKeys.length })}
|
||||
</Button>
|
||||
<Button icon={<TagsOutlined />} onClick={() => setBulkGroupOpen(true)}>
|
||||
{t('pages.clients.assignGroupSelected', { count: selectedRowKeys.length })}
|
||||
</Button>
|
||||
<Tag
|
||||
color="blue"
|
||||
closable
|
||||
onClose={() => setSelectedRowKeys([])}
|
||||
style={{ marginInlineEnd: 0, padding: '4px 8px', fontSize: 13 }}
|
||||
>
|
||||
{t('pages.clients.selectedCount', { count: selectedRowKeys.length })}
|
||||
</Tag>
|
||||
<Button icon={<UsergroupAddOutlined />} onClick={() => setBulkAttachOpen(true)}>
|
||||
{t('pages.clients.attachSelected', { count: selectedRowKeys.length })}
|
||||
{!isMobile && t('pages.clients.attach')}
|
||||
</Button>
|
||||
<Button danger icon={<UsergroupDeleteOutlined />} onClick={() => setBulkDetachOpen(true)}>
|
||||
{t('pages.clients.detachSelected', { count: selectedRowKeys.length })}
|
||||
</Button>
|
||||
<Button icon={<LinkOutlined />} onClick={() => setSubLinksOpen(true)}>
|
||||
{t('pages.clients.subLinksSelected', { count: selectedRowKeys.length })}
|
||||
</Button>
|
||||
<Button danger icon={<DeleteOutlined />} onClick={onBulkDelete}>
|
||||
{t('pages.clients.deleteSelected', { count: selectedRowKeys.length })}
|
||||
{!isMobile && t('pages.clients.detach')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
@@ -812,33 +809,64 @@ export default function ClientsPage() {
|
||||
trigger={['click']}
|
||||
placement="bottomRight"
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: 'bulk',
|
||||
icon: <UsergroupAddOutlined />,
|
||||
label: t('pages.clients.bulk'),
|
||||
onClick: () => setBulkAddOpen(true),
|
||||
},
|
||||
{
|
||||
key: 'resetAll',
|
||||
icon: <RetweetOutlined />,
|
||||
label: t('pages.clients.resetAllTraffics'),
|
||||
onClick: onResetAllTraffics,
|
||||
},
|
||||
{
|
||||
key: 'delDepleted',
|
||||
icon: <RestOutlined />,
|
||||
label: t('pages.clients.delDepleted'),
|
||||
danger: true,
|
||||
onClick: onDelDepleted,
|
||||
},
|
||||
],
|
||||
items: selectedRowKeys.length > 0
|
||||
? [
|
||||
{
|
||||
key: 'adjust',
|
||||
icon: <ClockCircleOutlined />,
|
||||
label: t('pages.clients.adjust'),
|
||||
onClick: () => setBulkAdjustOpen(true),
|
||||
},
|
||||
{
|
||||
key: 'group',
|
||||
icon: <TagsOutlined />,
|
||||
label: t('pages.clients.group'),
|
||||
onClick: () => setBulkGroupOpen(true),
|
||||
},
|
||||
{
|
||||
key: 'subLinks',
|
||||
icon: <LinkOutlined />,
|
||||
label: t('pages.clients.subLinks'),
|
||||
onClick: () => setSubLinksOpen(true),
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
key: 'bulk',
|
||||
icon: <UsergroupAddOutlined />,
|
||||
label: t('pages.clients.bulk'),
|
||||
onClick: () => setBulkAddOpen(true),
|
||||
},
|
||||
{
|
||||
key: 'resetAll',
|
||||
icon: <RetweetOutlined />,
|
||||
label: t('pages.clients.resetAllTraffics'),
|
||||
onClick: onResetAllTraffics,
|
||||
},
|
||||
{
|
||||
key: 'delDepleted',
|
||||
icon: <RestOutlined />,
|
||||
label: t('pages.clients.delDepleted'),
|
||||
danger: true,
|
||||
onClick: onDelDepleted,
|
||||
},
|
||||
],
|
||||
}}
|
||||
>
|
||||
<Button icon={<MoreOutlined />}>
|
||||
{!isMobile && t('more')}
|
||||
</Button>
|
||||
</Dropdown>
|
||||
{selectedRowKeys.length > 0 && (
|
||||
<Button
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={onBulkDelete}
|
||||
style={{ marginInlineStart: 'auto' }}
|
||||
>
|
||||
{!isMobile && t('delete')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -542,6 +542,10 @@
|
||||
"assignGroupPlaceholder": "Group name (leave blank to clear)",
|
||||
"assignGroupAssignedToast": "Assigned {count} client(s) to {group}",
|
||||
"assignGroupClearedToast": "Cleared group from {count} client(s)",
|
||||
"attach": "Attach",
|
||||
"adjust": "Adjust",
|
||||
"subLinks": "Sub links",
|
||||
"selectedCount": "{count} selected",
|
||||
"attachSelected": "Attach ({count})",
|
||||
"attachToInboundsTitle": "Attach {count} client(s) to inbound(s)",
|
||||
"attachToInboundsDesc": "Attaches the selected {count} client(s) (same UUID/password and shared traffic) to the chosen inbound(s). They keep their existing attachments too.",
|
||||
|
||||
@@ -513,6 +513,10 @@
|
||||
"deleteConfirmContent": "این کلاینت از تمام اینباندهای متصل حذف و سابقه ترافیک آن پاک میشود. این عمل غیرقابل بازگشت است.",
|
||||
"deleteSelected": "حذف ({count})",
|
||||
"adjustSelected": "تنظیم ({count})",
|
||||
"attach": "اتصال",
|
||||
"adjust": "تنظیم",
|
||||
"subLinks": "لینکهای ساب",
|
||||
"selectedCount": "{count} انتخابشده",
|
||||
"attachSelected": "اتصال ({count})",
|
||||
"attachToInboundsTitle": "اتصال {count} کلاینت به اینباند(ها)",
|
||||
"attachToInboundsDesc": "{count} کلاینت انتخابشده (با همان UUID/پسورد و ترافیک مشترک) به اینباند(های) انتخابی متصل میشوند. روی اینباندهای فعلی هم باقی میمانند.",
|
||||
|
||||
Reference in New Issue
Block a user