fix(ui): открывать выбранный сервер из очереди проблем

This commit is contained in:
Denozordec committed 2026-09-20 16:51:28 +07:00
1 parent 4ec32765df
commit 668c7c6823
5 files changed
+106 -14

No files matched your search

+37 -9
View File
@@ -1,6 +1,8 @@
"use client"
import { useEffect, useMemo, useRef, useState } from "react"
import { Suspense, useEffect, useMemo, useRef, useState } from "react"
import Link from "next/link"
import { useSearchParams } from "next/navigation"
import { serverCreateSchema } from "@mmapp/contracts/servers"
import { OperationGate, pollServers, serverOperationError, type PollResult } from "@/lib/server-operations"
import { Alert, AlertTitle, AlertDescription } from "@/components/reui/alert"
@@ -218,12 +220,17 @@ type TypeFilter = "all" | ServerType
type SheetMode = "add" | "edit"
// ════════════════════════════════════════════════════════════════════════════
export default function ServersPage() {
function ServersPageContent() {
const searchParams = useSearchParams()
const selectedServerId = searchParams.get("server")
const clearParams = new URLSearchParams(searchParams.toString())
clearParams.delete("server")
const allServersHref = "/servers" + (clearParams.size ? "?" + clearParams : "")
const { mode, backendUrl } = useDataSource()
const isLive = mode === "live"
const [serverList, setServerList] = useState<Server[]>([])
const [_backendOk, setBackendOk] = useState(false)
const [listState, setListState] = useState<"loading" | "ready" | "error">("loading")
const [search, setSearch] = useState("")
const [typeFilter, setTypeFilter] = useState<TypeFilter>("all")
const [advancedFilters, setAdvancedFilters] = useState(() => createFilterQuery())
@@ -283,25 +290,26 @@ export default function ServersPage() {
if (cancelled) return
formVersion.current++; testVersion.current++
setOpen(false); setDeleteTarget(null); setOperationError(""); setOperationMessage(""); setPollResults([])
setListState("loading")
})
if (mode !== "live") {
queueMicrotask(() => {
if (cancelled) return
setServerList(initialServers)
setBackendOk(false)
setListState("ready")
})
return invalidate
}
listServers(backendUrl)
.then(data => {
if (cancelled || version !== contextVersion.current) return
setBackendOk(true)
setListState("ready")
setServerList(data.map(toFrontendServer))
})
.catch((error) => {
if (cancelled || version !== contextVersion.current) return
setOperationError(serverOperationError(error))
setBackendOk(false)
setListState("error")
setServerList([])
})
return invalidate
@@ -550,15 +558,18 @@ export default function ServersPage() {
// ── derived ──────────────────────────────────────────────────────────────
const selectedServer = serverList.find(server => server.id === selectedServerId)
const filtered = useMemo(() => {
const base = serverList.filter(sv => {
if (selectedServerId !== null && sv.id !== selectedServerId) return false
if (typeFilter !== "all" && sv.type !== typeFilter) return false
if (!search) return true
const q = search.toLowerCase()
return sv.name.toLowerCase().includes(q) || sv.host.includes(q) || sv.site.toLowerCase().includes(q)
})
return applyFilterQuery(base, advancedFilters, SERVER_FILTER_ACCESSORS)
}, [serverList, search, typeFilter, advancedFilters])
}, [serverList, search, typeFilter, advancedFilters, selectedServerId])
const counts = useMemo(() => ({
all: serverList.length,
@@ -585,8 +596,8 @@ export default function ServersPage() {
crumbs={[{ label: "Управление" }, { label: "Серверы" }]}
actions={
<>
<Button variant="outline" size="sm" onClick={handlePollAll} disabled={!isLive || controlsBusy || serverList.length === 0}>
<RefreshCwIcon className={cn("size-4", pollAllBusy && "animate-spin")} />Проверить все
<Button variant="outline" size="sm" onClick={() => selectedServerId !== null ? selectedServer && void runPoll([selectedServer.id]) : void handlePollAll()} disabled={!isLive || controlsBusy || (selectedServerId !== null ? !selectedServer : serverList.length === 0)}>
<RefreshCwIcon className={cn("size-4", pollAllBusy && "animate-spin")} />{selectedServerId !== null ? "Проверить сервер" : "Проверить все"}
</Button>
<Button variant="outline" size="sm"><DownloadIcon className="size-4" />Экспорт</Button>
<Button size="sm" onClick={openAdd} disabled={controlsBusy}><PlusIcon className="size-4" />Добавить сервер</Button>
@@ -609,6 +620,19 @@ export default function ServersPage() {
</Alert>
)}
{selectedServerId !== null && (
<Alert variant={listState === "error" || (listState === "ready" && !selectedServer) ? "warning" : "info"} role="status" aria-label="Сервер по ссылке">
<AlertTitle className="line-clamp-none wrap-anywhere">{selectedServer ? "Выбран сервер: " + selectedServer.name : "Сервер по ссылке"}</AlertTitle>
<AlertDescription>
<p>{listState === "loading" ? "Загрузка сервера…" : listState === "error" ? "Не удалось загрузить сервер. Проверьте сообщение об ошибке выше." : selectedServer ? selectedServer.host + " · Список ограничен выбранным сервером. Сводка относится ко всем серверам." : "Сервер не найден или недоступен. Возможно, он удалён или у вас нет доступа."}</p>
<div className="flex flex-wrap gap-4">
<Link href={allServersHref} className="underline underline-offset-2">Все серверы</Link>
<Link href="/dashboard" className="underline underline-offset-2">К дашборду</Link>
</div>
</AlertDescription>
</Alert>
)}
<KpiStatGrid
aria-label="Сводка серверов"
items={[
@@ -934,3 +958,7 @@ export default function ServersPage() {
</div>
)
}
export default function ServersPage() {
return <Suspense fallback={<p role="status" className="p-6">Загрузка серверов…</p>}><ServersPageContent /></Suspense>
}