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> </div>
)} )}
{/* Выбор сервера - показываем список когда сервер не выбран */} {/* Селектор серверов на одной странице (Tabler UI) */}
{!selectedServer && ( <div className="card mb-3">
<div className="card"> <div className="card-header">
<div className="card-header"> <h3 className="card-title d-flex align-items-center gap-2">
<h3 className="card-title"> <IconServer className="icon" />
<IconServer className="icon me-2" /> Серверы
Выберите сервер для работы <span className="badge bg-blue-lt text-blue">{servers.length || 0}</span>
</h3> {selectedServer && (
<div className="card-actions"> <span className="badge bg-green-lt text-green">Выбран: {selectedServer.name}</span>
<button )}
className="btn btn-primary" </h3>
onClick={() => setServerConfigModalOpen(true)} <div className="card-actions">
> <button
<IconPlus className="icon" /> className="btn btn-outline-primary btn-sm"
Добавить сервер onClick={() => setServerConfigModalOpen(true)}
</button> >
</div> <IconPlus className="icon" />
Добавить сервер
</button>
</div> </div>
<div className="card-body"> </div>
{servers.length === 0 ? ( <div className="card-body">
<div className="empty"> {servers.length === 0 ? (
<div className="empty-icon"> <div className="empty">
<IconServer size={48} className="text-muted" /> <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> </div>
<p className="empty-title">Нет серверов</p> <div className="list-group list-group-flush border rounded">
<p className="empty-subtitle text-muted"> {activeServers.length > 0 && (
Создайте первый сервер для работы с фильтрами <div className="list-group-header px-3 pt-3 pb-2 text-uppercase text-muted small">Активные</div>
</p> )}
<div className="empty-action"> {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 <button
className="btn btn-primary" className="btn btn-link px-0 text-decoration-none"
onClick={() => setServerConfigModalOpen(true)} onClick={() => setServerListModalOpen(true)}
> >
<IconPlus className="icon" /> <IconSettings size={16} className="me-1" />
Добавить первый сервер Управление серверами
</button> </button>
</div> </div>
</div> </div>
) : ( <div className="col-12 col-lg-8">
<> {selectedServer ? (
{/* Поиск */} <div className="alert alert-info d-flex align-items-center" role="alert">
<div className="mb-3"> <IconServer className="me-2" />
<div className="input-icon"> <div>
<input Текущий сервер: <span className="fw-bold">{selectedServer.name}</span>
type="text" {selectedServer.description && (
className="form-control" <span className="text-muted ms-2">{selectedServer.description}</span>
placeholder="Поиск серверов..." )}
value={serverSearch} </div>
onChange={(e) => setServerSearch(e.target.value)}
/>
<span className="input-icon-addon">
<IconSearch />
</span>
</div> </div>
</div> ) : (
<div className="alert alert-warning d-flex align-items-center" role="alert">
{/* Список серверов */} <IconAlertTriangle className="me-2" />
<div className="row g-3"> <div>Выберите сервер слева, чтобы работать с фильтрами.</div>
{/* Активные серверы */} </div>
{activeServers.length > 0 && ( )}
<> </div>
<div className="col-12"> </div>
<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> </div>
)} </div>
{/* Секция фильтров выбранного сервера */} {/* Секция фильтров выбранного сервера */}
{selectedServer && ( {selectedServer && (