46 lines
3.3 KiB
TypeScript
46 lines
3.3 KiB
TypeScript
"use client"
|
||
|
||
import { useEffect, useMemo, type ReactNode } from "react"
|
||
import { useSearchParams } from "next/navigation"
|
||
import type { HomeEntry } from "@/lib/route-optimizer-data"
|
||
import { Button } from "@/components/ui/button"
|
||
import { Input } from "@/components/ui/input"
|
||
|
||
export function OptimizerHomesList({ homes, children }: { homes: HomeEntry[]; children: (entry: HomeEntry) => ReactNode }) {
|
||
const params = useSearchParams()
|
||
const query = params.get("home_q") ?? ""
|
||
const rawPage = params.get("home_page") ?? "1"
|
||
const requestedPage = /^[1-9]\d*$/.test(rawPage) && Number.isSafeInteger(Number(rawPage)) ? Number(rawPage) - 1 : 0
|
||
const filtered = useMemo(() => {
|
||
const value = query.trim().toLowerCase()
|
||
return value ? homes.filter(({ home }) => `${home.label} ${home.id} ${home.ip} ${home.site} ${home.country}`.toLowerCase().includes(value)) : homes
|
||
}, [homes, query])
|
||
const pages = Math.max(1, Math.ceil(filtered.length / 5))
|
||
const page = Math.min(requestedPage, pages - 1)
|
||
function change(queryValue: string, pageValue: number, push = false) {
|
||
const url = new URL(window.location.href)
|
||
if (queryValue) url.searchParams.set("home_q", queryValue); else url.searchParams.delete("home_q")
|
||
if (pageValue) url.searchParams.set("home_page", String(pageValue + 1)); else url.searchParams.delete("home_page")
|
||
window.history[push ? "pushState" : "replaceState"](null, "", url.pathname + url.search + url.hash)
|
||
}
|
||
useEffect(() => {
|
||
if (requestedPage < pages) return
|
||
const url = new URL(window.location.href)
|
||
if (page) url.searchParams.set("home_page", String(page + 1)); else url.searchParams.delete("home_page")
|
||
window.history.replaceState(null, "", url.pathname + url.search + url.hash)
|
||
}, [requestedPage, pages, page])
|
||
const controlled = homes.length > 5 || !!query || requestedPage > 0
|
||
return <section aria-label="Домашние устройства оптимизатора" className="flex min-w-0 flex-col gap-4">
|
||
{controlled && <div className="rounded-xl border border-border bg-background p-4 flex min-w-0 flex-col gap-3">
|
||
<Input aria-label="Поиск домашних устройств оптимизатора" placeholder="Имя, IP или площадка…" value={query} onChange={event => change(event.target.value, 0)} />
|
||
<p role="status" className="text-xs text-muted-foreground">Показаны {filtered.length ? page * 5 + 1 : 0}–{Math.min((page + 1) * 5, filtered.length)} из {filtered.length} устройств. Всего {homes.length}.</p>
|
||
<div className="flex flex-wrap gap-2">
|
||
<Button type="button" variant="outline" className="min-h-11" disabled={!page} aria-label="Предыдущая страница домашних устройств" onClick={() => change(query, page - 1, true)}>Назад</Button>
|
||
<Button type="button" variant="outline" className="min-h-11" disabled={page >= pages - 1} aria-label="Следующая страница домашних устройств" onClick={() => change(query, page + 1, true)}>Далее</Button>
|
||
</div>
|
||
{!filtered.length && <p className="text-sm text-muted-foreground">Домашние устройства по запросу не найдены.</p>}
|
||
</div>}
|
||
{filtered.slice(page * 5, (page + 1) * 5).map(children)}
|
||
</section>
|
||
}
|