refactor(FilterManager): simplify notification handling and improve layout structure for enhanced user experience
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m0s

This commit is contained in:
2026-02-16 21:19:05 +07:00
parent 8ad4f23a49
commit 5af5abb02d
+60 -22
View File
@@ -1740,14 +1740,7 @@ function FilterManager() {
return ( return (
<div className="page-wrapper"> <>
<PageHeader
title="Управление фильтрами frouting"
icon={<IconFilter size={24} />}
/>
<div className="page-body">
<div className="container-xl">
{/* Уведомления */} {/* Уведомления */}
{error && ( {error && (
<div className="alert alert-danger alert-dismissible" role="alert"> <div className="alert alert-danger alert-dismissible" role="alert">
@@ -1764,6 +1757,13 @@ function FilterManager() {
</div> </div>
)} )}
<PageHeader
title="Управление фильтрами frouting"
icon={<IconFilter size={24} />}
/>
<div className="row">
<div className="col-12">
{/* Селектор серверов на одной странице (Tabler UI) */} {/* Селектор серверов на одной странице (Tabler UI) */}
<div className="card mb-3"> <div className="card mb-3">
<div className="card-header"> <div className="card-header">
@@ -2054,10 +2054,14 @@ function FilterManager() {
<div className="alert alert-info mb-3"> <div className="alert alert-info mb-3">
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
<IconGripVertical size={18} className="me-2" /> <IconGripVertical size={18} className="me-2" />
<span>Перетащите строки за иконку <IconGripVertical size={14} className="mx-1" /> чтобы изменить приоритет</span> <span>
Перетащите строки за иконку{' '}
<IconGripVertical size={14} className="mx-1" /> чтобы изменить приоритет
</span>
</div> </div>
<div className="text-muted small mt-1"> <div className="text-muted small mt-1">
<strong>Важно:</strong> В RouterOS правила внизу списка имеют <strong>высший приоритет</strong> (переопределяют верхние) <strong>Важно:</strong> В RouterOS правила внизу списка имеют{' '}
<strong>высший приоритет</strong> (переопределяют верхние)
</div> </div>
</div> </div>
<DndContext <DndContext
@@ -2080,27 +2084,58 @@ function FilterManager() {
/> />
</th> </th>
<th style={{ width: '60px' }}>#</th> <th style={{ width: '60px' }}>#</th>
<th style={{ width: '22%' }} className="cursor-pointer" onClick={() => toggleSort('community')}> <th
style={{ width: '22%' }}
className="cursor-pointer"
onClick={() => toggleSort('community')}
>
<span className="me-1">COMMUNITY</span> <span className="me-1">COMMUNITY</span>
{sortBy === 'community' && (sortDir === 'asc' ? <IconSortAscending size={16} /> : <IconSortDescending size={16} />)} {sortBy === 'community' &&
(sortDir === 'asc' ? (
<IconSortAscending size={16} />
) : (
<IconSortDescending size={16} />
))}
</th> </th>
<th style={{ width: '22%' }} className="cursor-pointer" onClick={() => toggleSort('gateway')}> <th
style={{ width: '22%' }}
className="cursor-pointer"
onClick={() => toggleSort('gateway')}
>
<span className="me-1">GATEWAY</span> <span className="me-1">GATEWAY</span>
{sortBy === 'gateway' && (sortDir === 'asc' ? <IconSortAscending size={16} /> : <IconSortDescending size={16} />)} {sortBy === 'gateway' &&
(sortDir === 'asc' ? (
<IconSortAscending size={16} />
) : (
<IconSortDescending size={16} />
))}
</th> </th>
<th style={{ width: '30%' }} className="cursor-pointer" onClick={() => toggleSort('description')}> <th
style={{ width: '30%' }}
className="cursor-pointer"
onClick={() => toggleSort('description')}
>
<span className="me-1">ОПИСАНИЕ</span> <span className="me-1">ОПИСАНИЕ</span>
{sortBy === 'description' && (sortDir === 'asc' ? <IconSortAscending size={16} /> : <IconSortDescending size={16} />)} {sortBy === 'description' &&
(sortDir === 'asc' ? (
<IconSortAscending size={16} />
) : (
<IconSortDescending size={16} />
))}
</th>
<th style={{ width: '120px' }} className="text-end">
ДЕЙСТВИЯ
</th> </th>
<th style={{ width: '120px' }} className="text-end">ДЕЙСТВИЯ</th>
</tr> </tr>
</thead> </thead>
<SortableContext <SortableContext
items={serverFilters.filter(f => f != null).map(f => filterKey(f))} items={serverFilters.filter((f) => f != null).map((f) => filterKey(f))}
strategy={verticalListSortingStrategy} strategy={verticalListSortingStrategy}
> >
<tbody> <tbody>
{serverFilters.filter(f => f != null).map((filter, index) => { {serverFilters
.filter((f) => f != null)
.map((filter, index) => {
const key = filterKey(filter); const key = filterKey(filter);
const isSelected = selectedFilterKeys.has(key); const isSelected = selectedFilterKeys.has(key);
// Показываем только если совпадает с поиском // Показываем только если совпадает с поиском
@@ -2118,7 +2153,7 @@ function FilterManager() {
key={key} key={key}
filter={filter} filter={filter}
index={index} index={index}
totalCount={serverFilters.filter(f => f != null).length} totalCount={serverFilters.filter((f) => f != null).length}
filterKey={key} filterKey={key}
isSelected={isSelected} isSelected={isSelected}
onToggleSelect={() => toggleSelectFilter(key)} onToggleSelect={() => toggleSelectFilter(key)}
@@ -2145,7 +2180,10 @@ function FilterManager() {
<button className="btn btn-outline-danger btn-sm" onClick={deleteSelectedFilters}> <button className="btn btn-outline-danger btn-sm" onClick={deleteSelectedFilters}>
<IconTrash className="me-2" /> Удалить выбранные <IconTrash className="me-2" /> Удалить выбранные
</button> </button>
<button className="btn btn-outline-secondary btn-sm" onClick={() => setSelectedFilterKeys(new Set())}> <button
className="btn btn-outline-secondary btn-sm"
onClick={() => setSelectedFilterKeys(new Set())}
>
<IconX className="me-2" /> Отменить выбор <IconX className="me-2" /> Отменить выбор
</button> </button>
</div> </div>
@@ -2635,7 +2673,7 @@ function FilterManager() {
onCancel={() => setConfirmState({ open: false, text: '', onConfirm: null })} onCancel={() => setConfirmState({ open: false, text: '', onConfirm: null })}
onConfirm={() => confirmState.onConfirm?.()} onConfirm={() => confirmState.onConfirm?.()}
/> />
</div> </>
); );
} }