feat: search fish catalog
This commit is contained in:
@@ -8,14 +8,17 @@ import Pagination from "../../components/Pagination.astro";
|
|||||||
import { api, type DictionaryItem, type MediaAsset } from "../../lib/api";
|
import { api, type DictionaryItem, type MediaAsset } from "../../lib/api";
|
||||||
import { findMediaByLabel } from "../../lib/media";
|
import { findMediaByLabel } from "../../lib/media";
|
||||||
const params = Astro.url.searchParams;
|
const params = Astro.url.searchParams;
|
||||||
|
const search = params.get("q")?.trim() ?? "";
|
||||||
const pageLimit = 48;
|
const pageLimit = 48;
|
||||||
const requestedOffset = Number(params.get("offset") ?? 0);
|
const requestedOffset = Number(params.get("offset") ?? 0);
|
||||||
let offset = Number.isInteger(requestedOffset) && requestedOffset >= 0 ? requestedOffset : 0;
|
let offset = Number.isInteger(requestedOffset) && requestedOffset >= 0 ? requestedOffset : 0;
|
||||||
let fishes: DictionaryItem[] = [], allFishes: DictionaryItem[] = [], media: MediaAsset[] = [], unavailable = false;
|
let fishes: DictionaryItem[] = [], allFishes: DictionaryItem[] = [], media: MediaAsset[] = [], unavailable = false;
|
||||||
try {
|
try {
|
||||||
[allFishes, media] = await Promise.all([api<DictionaryItem[]>("/api/v1/fishes?limit=500"), api<MediaAsset[]>("/api/v1/media/catalog?entity_type=fish")]);
|
[allFishes, media] = await Promise.all([api<DictionaryItem[]>("/api/v1/fishes?limit=500"), api<MediaAsset[]>("/api/v1/media/catalog?entity_type=fish")]);
|
||||||
if (allFishes.length > 0 && offset >= allFishes.length) offset = Math.floor((allFishes.length - 1) / pageLimit) * pageLimit;
|
const matchingFishes = search ? allFishes.filter(item => item.name_ru.toLocaleLowerCase("ru-RU").includes(search.toLocaleLowerCase("ru-RU"))) : allFishes;
|
||||||
fishes = allFishes.slice(offset, offset + pageLimit);
|
if (matchingFishes.length > 0 && offset >= matchingFishes.length) offset = Math.floor((matchingFishes.length - 1) / pageLimit) * pageLimit;
|
||||||
|
fishes = matchingFishes.slice(offset, offset + pageLimit);
|
||||||
|
allFishes = matchingFishes;
|
||||||
} catch { unavailable = true; }
|
} catch { unavailable = true; }
|
||||||
if (unavailable) {
|
if (unavailable) {
|
||||||
Astro.response.status = 503;
|
Astro.response.status = 503;
|
||||||
@@ -25,6 +28,7 @@ if (unavailable) {
|
|||||||
---
|
---
|
||||||
<Layout title="Все виды рыб Russian Fishing 4 — RF4 Spotter" description="Каталог рыб RF4 со свежими точками, уловами, приманками и прозрачными источниками данных." noindex={unavailable} errorPage={unavailable}>
|
<Layout title="Все виды рыб Russian Fishing 4 — RF4 Spotter" description="Каталог рыб RF4 со свежими точками, уловами, приманками и прозрачными источниками данных." noindex={unavailable} errorPage={unavailable}>
|
||||||
<PageHero eyebrow="Справочник RF4" title="Рыбы" description="Выберите вид, чтобы увидеть свежие подтверждённые точки и полевые сигналы." variant="fish" count={allFishes.length} />
|
<PageHero eyebrow="Справочник RF4" title="Рыбы" description="Выберите вид, чтобы увидеть свежие подтверждённые точки и полевые сигналы." variant="fish" count={allFishes.length} />
|
||||||
{unavailable ? <StatePanel tone="unavailable" title="Каталог временно недоступен" description="Не показываем непроверенный список. Попробуйте обновить страницу позже." /> : <nav class="catalog-grid content-grid" aria-label="Виды рыб">{fishes.map((fish,index) => { const asset = findMediaByLabel(media, fish.name_ru); return <a href={`/fish/${fish.slug}`}><span>Вид рыбы · {String(offset + index + 1).padStart(2,"0")}</span><strong>{fish.name_ru}</strong><i>Открыть <b>→</b></i>{asset ? <EntityMedia asset={asset} compact /> : <FishSilhouette name={fish.name_ru} size={92}/>}</a>; })}</nav>}
|
<form class="record-filters content-grid" method="get" aria-label="Поиск рыбы"><label>Поиск по названию<input name="q" value={search} maxlength="100" placeholder="Например, щука" /></label><button data-action="primary">Найти</button>{search && <a data-action="quiet" href="/fish">Сбросить</a>}</form>
|
||||||
|
{unavailable ? <StatePanel tone="unavailable" title="Каталог временно недоступен" description="Не показываем непроверенный список. Попробуйте обновить страницу позже." /> : fishes.length ? <nav class="catalog-grid content-grid" aria-label="Виды рыб">{fishes.map((fish,index) => { const asset = findMediaByLabel(media, fish.name_ru); return <a href={`/fish/${fish.slug}`}><span>Вид рыбы · {String(offset + index + 1).padStart(2,"0")}</span><strong>{fish.name_ru}</strong><i>Открыть <b>→</b></i>{asset ? <EntityMedia asset={asset} compact /> : <FishSilhouette name={fish.name_ru} size={92}/>}</a>; })}</nav> : <StatePanel title="Ничего не найдено" description="Попробуйте изменить запрос или открыть полный справочник рыбы." />}
|
||||||
{!unavailable && <Pagination path="/fish" params={params} total={allFishes.length} limit={pageLimit} offset={offset} itemLabel="видов" />}
|
{!unavailable && <Pagination path="/fish" params={params} total={allFishes.length} limit={pageLimit} offset={offset} itemLabel="видов" />}
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
Reference in New Issue
Block a user