feat: add searchable fish and waterbody pages
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
---
|
||||
import ActivityCard from "../../components/ActivityCard.astro";
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
import { api, plural, type Activity, type DictionaryItem } from "../../lib/api";
|
||||
const { slug } = Astro.params;
|
||||
let water: DictionaryItem | undefined, items: Activity[] = [], unavailable = false;
|
||||
try {
|
||||
const waters = await api<DictionaryItem[]>("/api/v1/waterbodies?limit=500");
|
||||
water = waters.find(item => item.slug === slug);
|
||||
if (water) items = await api<Activity[]>(`/api/v1/activity?hours=72&waterbody=${encodeURIComponent(water.slug)}&limit=100`);
|
||||
} catch { unavailable = true; }
|
||||
if (!water && !unavailable) Astro.response.status = 404;
|
||||
const fishes = [...new Map(items.map(item => [item.fish_slug, item.fish])).entries()];
|
||||
const schema = water ? { "@context":"https://schema.org", "@type":"CollectionPage", name:`Что ловить на ${water.name_ru} в RF4`, url:`https://rf4spotter.ru/waterbodies/${water.slug}` } : null;
|
||||
---
|
||||
<Layout title={water ? `${water.name_ru} в RF4 — рыба и свежие точки` : "Водоём не найден — RF4 Spotter"} description={water ? `${water.name_ru} в Russian Fishing 4: свежие координаты, активные виды рыб, приманки и источники наблюдений.` : "Такого водоёма нет в каталоге RF4 Spotter."} noindex={!water} structuredData={schema}>
|
||||
<a class="back" href="/waterbodies">← Все водоёмы</a>
|
||||
<section class="catalog-hero content-grid"><span class="overline">Свежие данные за 72 часа</span><h1>{water?.name_ru ?? "Водоём не найден"}</h1>{water && <p>{items.length} {plural(items.length,["активная точка","активные точки","активных точек"])} для {fishes.length} {plural(fishes.length,["вида рыбы","видов рыб","видов рыб"])}.</p>}</section>
|
||||
{unavailable ? <div class="state content-grid"><h2>Данные временно недоступны</h2></div> : !water ? <div class="state content-grid"><h2>Такого водоёма нет в справочнике</h2><a href="/waterbodies">Открыть каталог</a></div> : <section class="catalog-results content-grid"><aside><span class="overline">Рыбы</span>{fishes.length ? <nav>{fishes.map(([fishSlug,name]) => <a href={`/waterbodies/${water!.slug}/${fishSlug}`}>{name}</a>)}</nav> : <p>Свежих подтверждённых видов пока нет.</p>}</aside><div>{items.length ? items.map(item => <ActivityCard item={item}/>) : <div class="state"><h2>Свежих точек пока нет</h2><p>Проверьте позже или посмотрите полевые сигналы на главной.</p></div>}</div></section>}
|
||||
</Layout>
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
import ActivityCard from "../../../components/ActivityCard.astro";
|
||||
import Layout from "../../../layouts/Layout.astro";
|
||||
import { api, plural, type Activity, type DictionaryItem } from "../../../lib/api";
|
||||
const { slug, fish: fishSlug } = Astro.params;
|
||||
let water: DictionaryItem | undefined, fish: DictionaryItem | undefined, items: Activity[] = [], unavailable = false;
|
||||
try {
|
||||
const [waters, fishes] = await Promise.all([api<DictionaryItem[]>("/api/v1/waterbodies?limit=500"), api<DictionaryItem[]>("/api/v1/fishes?limit=500")]);
|
||||
water = waters.find(item => item.slug === slug); fish = fishes.find(item => item.slug === fishSlug);
|
||||
if (water && fish) items = await api<Activity[]>(`/api/v1/activity?hours=72&waterbody=${encodeURIComponent(water.slug)}&fish=${encodeURIComponent(fish.slug)}&limit=100`);
|
||||
} catch { unavailable = true; }
|
||||
if ((!water || !fish) && !unavailable) Astro.response.status = 404;
|
||||
const valid = Boolean(water && fish);
|
||||
const schema = valid ? { "@type":"CollectionPage", name:`${fish!.name_ru} на ${water!.name_ru} в RF4`, url:`https://rf4spotter.ru/waterbodies/${water!.slug}/${fish!.slug}` } : null;
|
||||
---
|
||||
<Layout title={valid ? `${fish!.name_ru} на ${water!.name_ru} в RF4 — точки и приманки` : "Страница не найдена — RF4 Spotter"} description={valid ? `Где ловить ${fish!.name_ru} на ${water!.name_ru} в Russian Fishing 4: свежие координаты, приманки, активность и источники.` : "Такого сочетания рыбы и водоёма нет в каталоге."} noindex={!valid} structuredData={schema}>
|
||||
<a class="back" href={water ? `/waterbodies/${water.slug}` : "/waterbodies"}>← К водоёму</a>
|
||||
<section class="catalog-hero content-grid"><span class="overline">{water?.name_ru ?? "Каталог RF4"}</span><h1>{fish?.name_ru ?? "Данные не найдены"}</h1>{valid && <p>{items.length} {plural(items.length,["свежая точка","свежие точки","свежих точек"])} за последние 72 часа.</p>}</section>
|
||||
{unavailable ? <div class="state content-grid"><h2>Данные временно недоступны</h2></div> : !valid ? <div class="state content-grid"><h2>Такого сочетания нет в справочнике</h2><a href="/">Вернуться на главную</a></div> : <section class="catalog-results catalog-results--single content-grid"><div>{items.length ? items.map(item => <ActivityCard item={item}/>) : <div class="state"><h2>Свежих точек пока нет</h2><p>Данные появятся после новых подтверждённых наблюдений.</p></div>}</div></section>}
|
||||
</Layout>
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
import { api, type DictionaryItem } from "../../lib/api";
|
||||
let waters: DictionaryItem[] = [], unavailable = false;
|
||||
try { waters = await api<DictionaryItem[]>("/api/v1/waterbodies?limit=500"); } catch { unavailable = true; }
|
||||
---
|
||||
<Layout title="Все водоёмы Russian Fishing 4 — RF4 Spotter" description="Каталог водоёмов RF4 со свежими точками, рыбами, приманками и прозрачными источниками данных.">
|
||||
<section class="catalog-hero content-grid"><span class="overline">Карта водоёмов RF4</span><h1>Водоёмы</h1><p>Откройте водоём, чтобы увидеть активные виды рыб и последние подтверждённые точки.</p></section>
|
||||
{unavailable ? <div class="state content-grid"><h2>Каталог временно недоступен</h2></div> : <nav class="catalog-grid content-grid" aria-label="Водоёмы">{waters.map(water => <a href={`/waterbodies/${water.slug}`}><span>Водоём</span><strong>{water.name_ru}</strong><i>→</i></a>)}</nav>}
|
||||
</Layout>
|
||||
Reference in New Issue
Block a user