34 lines
2.2 KiB
TypeScript
34 lines
2.2 KiB
TypeScript
"use client"
|
||
|
||
import { useMemo, useState, type ReactNode } from "react"
|
||
import { Button } from "@/components/ui/button"
|
||
import { Input } from "@/components/ui/input"
|
||
|
||
export function OspfDetailList<T>({ items, label, getSearchValue, renderItem }: {
|
||
items: T[]; label: string; getSearchValue: (item: T) => string
|
||
renderItem: (item: T, index: number) => ReactNode
|
||
}) {
|
||
const [query, setQuery] = useState("")
|
||
const [page, setPage] = useState(0)
|
||
const filtered = useMemo(() => {
|
||
const value = query.trim().toLowerCase()
|
||
return value ? items.filter(item => getSearchValue(item).toLowerCase().includes(value)) : items
|
||
}, [items, query, getSearchValue])
|
||
const pages = Math.max(1, Math.ceil(filtered.length / 50))
|
||
if (page >= pages) setPage(pages - 1)
|
||
const current = Math.min(page, pages - 1)
|
||
const controlled = items.length > 50 || !!query
|
||
return <section aria-label={label} className="min-w-0">
|
||
{controlled && <div className="flex min-w-0 flex-col gap-2 mb-2">
|
||
<Input aria-label={`Поиск: ${label}`} placeholder="Поиск во всём списке…" value={query} onChange={event => { setQuery(event.target.value); setPage(0) }} />
|
||
<p role="status" className="text-xs text-muted-foreground">Показаны {filtered.length ? current * 50 + 1 : 0}–{Math.min((current + 1) * 50, filtered.length)} из {filtered.length}. Всего {items.length}.</p>
|
||
<div className="flex flex-wrap gap-2">
|
||
<Button type="button" variant="outline" className="min-h-11" aria-label={`Предыдущая страница: ${label}`} disabled={!current} onClick={() => setPage(current - 1)}>Назад</Button>
|
||
<Button type="button" variant="outline" className="min-h-11" aria-label={`Следующая страница: ${label}`} disabled={current >= pages - 1} onClick={() => setPage(current + 1)}>Далее</Button>
|
||
</div>
|
||
{!filtered.length && <p className="text-xs text-muted-foreground">По вашему запросу ничего не найдено.</p>}
|
||
</div>}
|
||
<div className="flex flex-col gap-0 divide-y divide-border">{filtered.slice(current * 50, (current + 1) * 50).map((item, index) => renderItem(item, current * 50 + index))}</div>
|
||
</section>
|
||
}
|