diff --git a/frontend/src/ASNsNewManager.jsx b/frontend/src/ASNsNewManager.jsx index 6e43dcb..4910d8a 100644 --- a/frontend/src/ASNsNewManager.jsx +++ b/frontend/src/ASNsNewManager.jsx @@ -874,6 +874,14 @@ function ASNsNewManager() { + + + + {}, @@ -12,109 +14,60 @@ function BulkActionsBar({ onDelete = null, onEdit = null, onExport = null, - customActions = [], // [{icon: Icon, label, onClick, variant}] - className = '' + customActions = [], + className = '', }) { - if (selectedCount === 0) return null + const actions = []; - const allSelected = selectedCount === totalCount + if (onExport) { + actions.push({ + type: 'button', + label: 'Экспорт', + icon: IconDownload, + onClick: onExport, + title: 'Экспортировать выбранное', + }); + } + + if (onEdit) { + actions.push({ + type: 'button', + label: 'Изменить', + icon: IconEdit, + onClick: onEdit, + title: 'Редактировать выбранное', + }); + } + + customActions.forEach((action) => { + actions.push({ + type: 'button', + label: action.label, + icon: action.icon, + onClick: action.onClick, + variant: action.variant === 'btn-outline-danger' || action.variant === 'danger' ? 'danger' : undefined, + title: action.title, + }); + }); + + if (onDelete) { + actions.push({ + type: 'button', + label: 'Удалить', + icon: IconTrash, + onClick: onDelete, + variant: 'danger', + title: 'Удалить выбранное', + }); + } return ( -
-
-
- {/* Левая часть: информация о выборе */} -
-
- - - Выбрано: {selectedCount} - -
- - {/* Кнопка выбрать все / снять выбор */} - {!allSelected && totalCount > selectedCount && ( - - )} - - -
- - {/* Правая часть: действия */} -
- {/* Экспорт */} - {onExport && ( - - )} - - {/* Редактирование */} - {onEdit && ( - - )} - - {/* Пользовательские действия */} - {customActions.map((action, idx) => { - const Icon = action.icon - return ( - - ) - })} - - {/* Удаление */} - {onDelete && ( - - )} -
-
-
-
- ) + + ); } -export default BulkActionsBar - +export default BulkActionsBar; diff --git a/frontend/src/components/FloatingBulkActionsBar.jsx b/frontend/src/components/FloatingBulkActionsBar.jsx new file mode 100644 index 0000000..2015646 --- /dev/null +++ b/frontend/src/components/FloatingBulkActionsBar.jsx @@ -0,0 +1,185 @@ +import { IconX } from '@tabler/icons-react'; + +/** + * Всплывающая панель массовых действий в стиле со скрина: + * тёмная панель по центру снизу, счётчик "N выбрано", кнопки действий, закрыть. + * Используется на страницах типа Manager при выборе элементов. + * + * @param {number} selectedCount - количество выбранных элементов + * @param {() => void} onClearSelection - снять выделение (кнопка X) + * @param {Array<{ type: 'button', label: string, icon: React.ComponentType, onClick: () => void, variant?: 'danger', title?: string } | { type: 'dropdown', label: string, icon: React.ComponentType, items: Array<{ label: string, onClick: () => void }>, title?: string }>} actions - кнопки и выпадающие меню + */ +function FloatingBulkActionsBar({ selectedCount = 0, onClearSelection, actions = [] }) { + if (selectedCount === 0) return null; + + return ( +
+
+ {/* Счётчик выбранных */} +
+ + {selectedCount} + + выбрано +
+ + {/* Действия */} +
+ {actions.map((action, idx) => { + if (action.type === 'dropdown') { + const Icon = action.icon; + return ( +
+ +
+ {action.items?.map((item, i) => ( + + ))} +
+
+ ); + } + const Icon = action.icon; + const isDanger = action.variant === 'danger'; + return ( + + ); + })} + + {actions.length > 0 && ( +
+ )} + + +
+
+ + +
+ ); +} + +export default FloatingBulkActionsBar; diff --git a/frontend/src/components/server/ServerBulkActions.jsx b/frontend/src/components/server/ServerBulkActions.jsx index b2ad247..72b8bdb 100644 --- a/frontend/src/components/server/ServerBulkActions.jsx +++ b/frontend/src/components/server/ServerBulkActions.jsx @@ -1,12 +1,11 @@ import { IconTrash, - IconEdit, IconCopy, IconX, IconNetwork, IconDownload, - IconCheck } from '@tabler/icons-react'; +import FloatingBulkActionsBar from '../FloatingBulkActionsBar.jsx'; export default function ServerBulkActions({ selectedCount, @@ -15,169 +14,48 @@ export default function ServerBulkActions({ onBulkChangeTunnel, onBulkExport, onBulkCopyLinks, - tunnelTypes = ['GRE', 'IPSec', 'WireGuard', 'OpenVPN'] + tunnelTypes = ['GRE', 'IPSec', 'WireGuard', 'OpenVPN'], }) { - if (selectedCount === 0) return null; + const actions = [ + { + type: 'dropdown', + label: 'Туннель', + icon: IconNetwork, + title: 'Сменить тип туннеля', + items: tunnelTypes.map((tunnel) => ({ + label: tunnel, + onClick: () => onBulkChangeTunnel(tunnel), + })), + }, + { + type: 'button', + label: 'Ссылки', + icon: IconCopy, + onClick: onBulkCopyLinks, + title: 'Скопировать ссылки', + }, + { + type: 'button', + label: 'Экспорт', + icon: IconDownload, + onClick: onBulkExport, + title: 'Экспорт', + }, + { + type: 'button', + label: 'Удалить', + icon: IconTrash, + onClick: onBulkDelete, + variant: 'danger', + title: 'Удалить', + }, + ]; return ( -
-
- {/* Счётчик выбранных */} -
- - {selectedCount} - - выбрано -
- - {/* Действия */} -
- {/* Смена типа туннеля */} -
- -
-
Сменить тип туннеля
- {tunnelTypes.map(tunnel => ( - - ))} -
-
- - {/* Копировать ссылки */} - - - {/* Экспорт */} - - -
- - {/* Удалить */} - - - {/* Отменить выделение */} - -
-
- - -
+ ); }