refactor: Revamp FilterManager UI for server selection, enhancing layout and interaction with improved card design and search functionality.
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m53s

This commit is contained in:
2025-12-06 21:55:29 +07:00
parent 312c442d7c
commit 2b8470a7ca
+127 -124
View File
@@ -900,139 +900,142 @@ function FilterManager() {
</div>
)}
{/* Выбор сервера - показываем список когда сервер не выбран */}
{!selectedServer && (
<div className="card">
<div className="card-header">
<h3 className="card-title">
<IconServer className="icon me-2" />
Выберите сервер для работы
</h3>
<div className="card-actions">
<button
className="btn btn-primary"
onClick={() => setServerConfigModalOpen(true)}
>
<IconPlus className="icon" />
Добавить сервер
</button>
</div>
{/* Селектор серверов на одной странице (Tabler UI) */}
<div className="card mb-3">
<div className="card-header">
<h3 className="card-title d-flex align-items-center gap-2">
<IconServer className="icon" />
Серверы
<span className="badge bg-blue-lt text-blue">{servers.length || 0}</span>
{selectedServer && (
<span className="badge bg-green-lt text-green">Выбран: {selectedServer.name}</span>
)}
</h3>
<div className="card-actions">
<button
className="btn btn-outline-primary btn-sm"
onClick={() => setServerConfigModalOpen(true)}
>
<IconPlus className="icon" />
Добавить сервер
</button>
</div>
<div className="card-body">
{servers.length === 0 ? (
<div className="empty">
<div className="empty-icon">
<IconServer size={48} className="text-muted" />
</div>
<div className="card-body">
{servers.length === 0 ? (
<div className="empty">
<div className="empty-icon">
<IconServer size={48} className="text-muted" />
</div>
<p className="empty-title">Нет серверов</p>
<p className="empty-subtitle text-muted">
Создайте первый сервер для работы с фильтрами
</p>
<div className="empty-action">
<button
className="btn btn-primary"
onClick={() => setServerConfigModalOpen(true)}
>
<IconPlus className="icon" />
Добавить первый сервер
</button>
</div>
</div>
) : (
<div className="row g-3">
<div className="col-12 col-lg-4">
<div className="input-icon mb-2">
<input
type="text"
className="form-control"
placeholder="Поиск серверов..."
value={serverSearch}
onChange={(e) => setServerSearch(e.target.value)}
/>
<span className="input-icon-addon">
<IconSearch />
</span>
</div>
<p className="empty-title">Нет серверов</p>
<p className="empty-subtitle text-muted">
Создайте первый сервер для работы с фильтрами
</p>
<div className="empty-action">
<div className="list-group list-group-flush border rounded">
{activeServers.length > 0 && (
<div className="list-group-header px-3 pt-3 pb-2 text-uppercase text-muted small">Активные</div>
)}
{activeServers.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-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: '220px' }}>
{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: '220px' }}>
{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-primary"
onClick={() => setServerConfigModalOpen(true)}
className="btn btn-link px-0 text-decoration-none"
onClick={() => setServerListModalOpen(true)}
>
<IconPlus className="icon" />
Добавить первый сервер
<IconSettings size={16} className="me-1" />
Управление серверами
</button>
</div>
</div>
) : (
<>
{/* Поиск */}
<div className="mb-3">
<div className="input-icon">
<input
type="text"
className="form-control"
placeholder="Поиск серверов..."
value={serverSearch}
onChange={(e) => setServerSearch(e.target.value)}
/>
<span className="input-icon-addon">
<IconSearch />
</span>
<div className="col-12 col-lg-8">
{selectedServer ? (
<div className="alert alert-info d-flex align-items-center" role="alert">
<IconServer className="me-2" />
<div>
Текущий сервер: <span className="fw-bold">{selectedServer.name}</span>
{selectedServer.description && (
<span className="text-muted ms-2">{selectedServer.description}</span>
)}
</div>
</div>
</div>
{/* Список серверов */}
<div className="row g-3">
{/* Активные серверы */}
{activeServers.length > 0 && (
<>
<div className="col-12">
<h4 className="text-muted mb-0">Активные серверы</h4>
</div>
{activeServers.map(server => (
<div key={server.id} className="col-md-6 col-lg-4">
<div className="card card-sm card-link" onClick={() => setSelectedServer(server)} style={{ cursor: 'pointer' }}>
<div className="card-body">
<div className="d-flex align-items-center">
<div className="me-3">
<span className="avatar bg-primary-lt">
<IconServer />
</span>
</div>
<div className="flex-grow-1">
<div className="fw-bold">{server.name}</div>
{server.description && (
<div className="text-muted small text-truncate" style={{ maxWidth: '200px' }}>
{server.description}
</div>
)}
</div>
<div className="ms-auto">
<span className="badge bg-green-lt text-green">Активен</span>
</div>
</div>
</div>
</div>
</div>
))}
</>
)}
{/* Неактивные серверы */}
{inactiveServers.length > 0 && (
<>
<div className="col-12 mt-3">
<h4 className="text-muted mb-0">Неактивные серверы</h4>
</div>
{inactiveServers.map(server => (
<div key={server.id} className="col-md-6 col-lg-4">
<div className="card card-sm card-link" onClick={() => setSelectedServer(server)} style={{ cursor: 'pointer' }}>
<div className="card-body">
<div className="d-flex align-items-center">
<div className="me-3">
<span className="avatar bg-secondary-lt">
<IconServer className="text-muted" />
</span>
</div>
<div className="flex-grow-1">
<div className="fw-bold text-muted">{server.name}</div>
{server.description && (
<div className="text-muted small text-truncate" style={{ maxWidth: '200px' }}>
{server.description}
</div>
)}
</div>
<div className="ms-auto">
<span className="badge bg-red-lt text-red">Неактивен</span>
</div>
</div>
</div>
</div>
</div>
))}
</>
)}
</div>
</>
)}
</div>
) : (
<div className="alert alert-warning d-flex align-items-center" role="alert">
<IconAlertTriangle className="me-2" />
<div>Выберите сервер слева, чтобы работать с фильтрами.</div>
</div>
)}
</div>
</div>
)}
</div>
)}
</div>
{/* Секция фильтров выбранного сервера */}
{selectedServer && (