refactor: Revise FilterManager layout to enhance server selection interface with improved button actions and card design, streamlining user interaction and visual presentation.
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m58s

This commit is contained in:
2025-12-06 22:12:32 +07:00
parent a058df010a
commit c84d4367b1
+93 -116
View File
@@ -942,10 +942,10 @@ function FilterManager() {
</div> </div>
</div> </div>
) : ( ) : (
<div className="row g-3"> <>
{/* Левая колонка: список серверов */} {/* Тулбар: поиск + действия для выбранного сервера */}
<div className="col-12 col-xl-7"> <div className="d-flex flex-column flex-md-row gap-2 align-items-md-center mb-3">
<div className="input-icon mb-2"> <div className="input-icon flex-grow-1" style={{ minWidth: '240px' }}>
<input <input
type="text" type="text"
className="form-control" className="form-control"
@@ -957,131 +957,108 @@ function FilterManager() {
<IconSearch /> <IconSearch />
</span> </span>
</div> </div>
<div className="list-group list-group-flush border rounded"> <div className="d-flex flex-wrap gap-2">
{activeServers.length > 0 && ( <button
<div className="list-group-header px-3 pt-3 pb-2 text-uppercase text-muted small">Активные</div> className="btn btn-outline-primary"
)} onClick={handlePreviewConfig}
{activeServers.map(server => ( disabled={!selectedServer || serverFilters.length === 0}
<button
key={server.id}
className={`list-group-item list-group-item-action d-flex align-items-center ${selectedServer?.id === server.id ? 'active' : ''}`}
onClick={() => setSelectedServer(server)}
>
<span className="avatar bg-primary-lt me-3">
<IconServer size={16} />
</span>
<div className="flex-grow-1 text-start">
<div className="fw-bold">{server.name}</div>
{server.description && (
<div className="text-muted small text-truncate" style={{ maxWidth: '240px' }}>
{server.description}
</div>
)}
</div>
<span className="badge bg-green-lt text-green">Активен</span>
</button>
))}
{inactiveServers.length > 0 && (
<div className="list-group-header px-3 pt-3 pb-2 text-uppercase text-muted small">Неактивные</div>
)}
{inactiveServers.map(server => (
<button
key={server.id}
className={`list-group-item list-group-item-action d-flex align-items-center ${selectedServer?.id === server.id ? 'active' : ''}`}
onClick={() => setSelectedServer(server)}
>
<span className="avatar bg-secondary-lt me-3">
<IconServer size={16} />
</span>
<div className="flex-grow-1 text-start">
<div className="fw-bold">{server.name}</div>
{server.description && (
<div className="text-muted small text-truncate" style={{ maxWidth: '240px' }}>
{server.description}
</div>
)}
</div>
<span className="badge bg-secondary-lt text-secondary">Выкл</span>
</button>
))}
</div>
<div className="mt-2 text-end">
<button
className="btn btn-link px-0 text-decoration-none"
onClick={() => setServerListModalOpen(true)}
> >
<IconSettings size={16} className="me-1" /> <IconEye className="icon" />
Управление серверами Просмотр
</button>
<button
className="btn btn-outline-primary"
onClick={copyConfigToClipboard}
disabled={!selectedServer || serverFilters.length === 0}
>
<IconCopy className="icon" />
Копировать
</button>
<button
className="btn btn-success"
onClick={() => selectedServer && handleSaveServerConfig(selectedServer.id)}
disabled={!selectedServer || loading || serverFilters.length === 0}
>
<IconDeviceFloppy className="icon" />
Сохранить
</button> </button>
</div> </div>
</div> </div>
{/* Правая колонка: детали выбранного сервера / подсказка */} {/* Горизонтальный список серверов */}
<div className="col-12 col-xl-5"> <div className="d-flex flex-wrap gap-2 mb-2">
{selectedServer ? ( {activeServers.map(server => (
<div className="card card-sm"> <div
<div className="card-body"> key={server.id}
<div className="d-flex align-items-start mb-2"> className={`card card-sm cursor-pointer ${selectedServer?.id === server.id ? 'border-primary' : ''}`}
<span className="avatar bg-primary-lt me-3"> style={{ minWidth: '220px', maxWidth: '260px' }}
<IconServer size={18} /> onClick={() => setSelectedServer(server)}
</span> >
<div className="flex-grow-1"> <div className="card-body d-flex align-items-start gap-2">
<div className="d-flex align-items-center gap-2"> <span className="avatar bg-primary-lt">
<span className="fw-bold">{selectedServer.name}</span> <IconServer size={16} />
<span className="badge bg-green-lt text-green">Активен</span> </span>
</div> <div className="flex-grow-1">
{selectedServer.description && ( <div className="d-flex align-items-center gap-2">
<div className="text-muted small mt-1">{selectedServer.description}</div> <div className="fw-bold text-truncate" title={server.name}>{server.name}</div>
)} <span className="badge bg-green-lt text-green">Активен</span>
</div> </div>
</div> {server.description && (
<div className="text-muted small text-truncate" title={server.description}>
<div className="d-flex flex-wrap gap-3 text-muted small mb-2"> {server.description}
<span>Фильтров: <span className="badge bg-blue-lt text-blue">{serverFilters.length}</span></span> </div>
<span>Поиск: <code className="text-blue">{filterSearch || '—'}</code></span> )}
</div>
<div className="btn-list">
<button
className="btn btn-outline-primary"
onClick={handlePreviewConfig}
disabled={serverFilters.length === 0}
>
<IconEye className="icon" />
Просмотр
</button>
<button
className="btn btn-outline-primary"
onClick={copyConfigToClipboard}
disabled={serverFilters.length === 0}
>
<IconCopy className="icon" />
Копировать
</button>
<button
className="btn btn-success"
onClick={() => handleSaveServerConfig(selectedServer.id)}
disabled={loading || serverFilters.length === 0}
>
<IconDeviceFloppy className="icon" />
Сохранить
</button>
</div> </div>
</div> </div>
</div> </div>
) : ( ))}
<div className="card card-sm"> {inactiveServers.map(server => (
<div className="card-body d-flex align-items-start"> <div
<IconAlertTriangle className="me-3 text-warning" /> key={server.id}
<div> className={`card card-sm cursor-pointer ${selectedServer?.id === server.id ? 'border-primary' : ''}`}
<div className="fw-bold">Сначала выберите сервер</div> style={{ minWidth: '220px', maxWidth: '260px' }}
<div className="text-muted">Нажмите на сервер слева, чтобы открыть его фильтры.</div> onClick={() => setSelectedServer(server)}
>
<div className="card-body d-flex align-items-start gap-2">
<span className="avatar bg-secondary-lt">
<IconServer size={16} />
</span>
<div className="flex-grow-1">
<div className="d-flex align-items-center gap-2">
<div className="fw-bold text-muted text-truncate" title={server.name}>{server.name}</div>
<span className="badge bg-secondary-lt text-secondary">Выкл</span>
</div>
{server.description && (
<div className="text-muted small text-truncate" title={server.description}>
{server.description}
</div>
)}
</div> </div>
</div> </div>
</div> </div>
)} ))}
</div> </div>
</div>
<div className="d-flex justify-content-between align-items-center mt-2">
<div className="text-muted small">
{selectedServer ? (
<>
Текущий сервер: <span className="fw-bold">{selectedServer.name}</span>
<span className="badge bg-blue-lt text-blue ms-2">{serverFilters.length} фильтров</span>
</>
) : (
'Выберите сервер для работы с фильтрами'
)}
</div>
<button
className="btn btn-link px-0 text-decoration-none"
onClick={() => setServerListModalOpen(true)}
>
<IconSettings size={16} className="me-1" />
Управление серверами
</button>
</div>
</>
)} )}
</div> </div>
</div> </div>