feat: show catch freshness on spot details
This commit is contained in:
@@ -14,7 +14,7 @@ RF4DB/RF4-STAT/RF4MAP/RF4 Posts сначала принимаются в изо
|
||||
|
||||
Базовый SEO-контур готов для `rf4spotter.ru`: страницы имеют уникальные метаданные, canonical, Open Graph/Twitter Card, фирменное изображение 1200×630 и JSON-LD; доступны динамические `/robots.txt` и `/sitemap.xml`, административные и ошибочные страницы закрыты от индексации, добавлена собственная страница 404. Индексируемые каталоги рыб и водоёмов, detail-страницы и сочетания водоём + рыба строятся из актуального разрешённого справочника и включаются в sitemap. Подключены фирменные favicon/app icons, web manifest и production-кэширование статических ресурсов. Карточки активности показывают единый паспорт данных: источники, свежесть, полноту и уровень доверия. На `/status` опубликована легенда цветов всех источников и статусов качества.
|
||||
|
||||
Публичные точки используют постоянные читаемые адреса вида `/spots/kuori-85x92`; старые UUID-адреса остаются совместимыми и перенаправляются на канонический URL. На странице точки координаты дополнительно показаны фирменным радаром, который не имитирует отсутствующую географию водоёма, а уловы за 72 часа — шкалой-леской с 12-часовым шагом. Карточки активности и каталог дополнены лёгкими SVG-силуэтами рыб без внешних графических зависимостей. Пустые и аварийные состояния используют собственную CSS-иллюстрацию поплавка; анимация учитывает системное ограничение движения.
|
||||
Публичные точки используют постоянные читаемые адреса вида `/spots/kuori-85x92`; старые UUID-адреса остаются совместимыми и перенаправляются на канонический URL. На странице точки координаты дополнительно показаны фирменным радаром, который не имитирует отсутствующую географию водоёма, а уловы за 72 часа — шкалой-леской с 12-часовым шагом. Каждый улов показывает источник, относительную свежесть и точную дату в подсказке. Карточки активности и каталог дополнены лёгкими SVG-силуэтами рыб без внешних графических зависимостей. Пустые и аварийные состояния используют собственную CSS-иллюстрацию поплавка; анимация учитывает системное ограничение движения.
|
||||
|
||||
Все пять community-парсеров подключены к отдельному scheduler-процессу. Состояние запусков и ошибок хранится в PostgreSQL, параллельный запуск одного источника блокируется, минимальный интервал жёстко ограничен 1800 секундами. Локально процесс включается профилем `docker compose --profile scheduler up -d`; detail-URL RF4MAP/RF4 Posts задаются переменными окружения.
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
import SourceBadge from "./SourceBadge.astro";
|
||||
import { ago, kg, type Catch } from "../lib/api";
|
||||
const { catches } = Astro.props as { catches: Catch[] };
|
||||
---
|
||||
<div class="catch-list">
|
||||
{catches.map(item => {
|
||||
const timestamp = item.caught_at ?? item.reported_at;
|
||||
return <article>
|
||||
<div><strong>{item.fish}</strong><span>{item.bait ?? "Приманка не указана"}</span><SourceBadge source={item.source_system} href={item.source_url}/></div>
|
||||
<div><strong>{kg(item.weight_g)}</strong><span>{item.player_name ?? "Анонимно"}</span><time datetime={timestamp} title={new Date(timestamp).toLocaleString("ru-RU")}>{ago(timestamp)}</time></div>
|
||||
</article>;
|
||||
})}
|
||||
</div>
|
||||
<style>
|
||||
time{margin-top:7px;color:#496357;font-size:10px;font-weight:750}
|
||||
</style>
|
||||
@@ -3,7 +3,8 @@ import Layout from "../../layouts/Layout.astro";
|
||||
import SourceBadge from "../../components/SourceBadge.astro";
|
||||
import CoordinateRadar from "../../components/CoordinateRadar.astro";
|
||||
import ActivityTimeline from "../../components/ActivityTimeline.astro";
|
||||
import { activityLevel, api, kg, plural, type Activity, type Catch, type Spot } from "../../lib/api";
|
||||
import CatchList from "../../components/CatchList.astro";
|
||||
import { activityLevel, api, plural, type Activity, type Catch, type Spot } from "../../lib/api";
|
||||
const { id } = Astro.params;
|
||||
let spot: Spot | null = null, catches: Catch[] = [], activity: Activity | null = null, unavailable = false;
|
||||
try {
|
||||
@@ -28,6 +29,12 @@ const breadcrumbs = spot ? { "@context": "https://schema.org", "@type": "Breadcr
|
||||
<section class="spot-hero"><div><span class="eyebrow">{spot.waterbody}</span><h1>Точка {spot.x}:{spot.y}</h1><p>{spot.description}</p></div><CoordinateRadar x={spot.x} y={spot.y}/></section>
|
||||
<div class="periods"><div><strong>{spot.catches_24h}</strong><span>за 24 часа</span></div><div><strong>{spot.catches_3d}</strong><span>за 3 дня</span></div><div><strong>{spot.catches_7d}</strong><span>за 7 дней</span></div></div>
|
||||
<ActivityTimeline catches={catches}/>
|
||||
<section class="detail-grid"><div><div class="section-heading"><h2>Последние уловы</h2></div>{catches.length ? <div class="catch-list">{catches.map(item => <article><div><strong>{item.fish}</strong><span>{item.bait ?? "Приманка не указана"}</span><SourceBadge source={item.source_system} href={item.source_url}/></div><div><strong>{kg(item.weight_g)}</strong><span>{item.player_name ?? "Анонимно"}</span></div></article>)}</div> : <div class="state compact-state"><h2>Уловов пока нет</h2><p>Для этой точки нет одобренных наблюдений.</p></div>}</div><aside><span class="eyebrow">Оценка за 24 часа</span>{activity && level ? <><h2>{activity.activity_score} / 100</h2><strong data-activity-level={level.short}>{level.description}</strong><div class="source-strip">{activity.sources.map(source => <SourceBadge source={source}/>)}</div><p>{activity.explanation}</p><p class="note">Основано на {activity.catches} {plural(activity.catches, ["наблюдении", "наблюдениях", "наблюдениях"])} от {activity.unique_players} {plural(activity.unique_players, ["игрока", "игроков", "игроков"])}. Уверенность: {activity.confidence_score}%.</p></> : <p>За последние 24 часа данных для расчёта нет.</p>}<span class="eyebrow">Лучшие приманки</span>{spot.top_baits.length ? <ol>{spot.top_baits.map(name => <li>{name}</li>)}</ol> : <p>Недостаточно данных.</p>}<p class="note">Учитываются только одобренные наблюдения.</p></aside></section>
|
||||
<section class="detail-grid">
|
||||
<div>
|
||||
<div class="section-heading"><h2>Последние уловы</h2></div>
|
||||
{catches.length ? <CatchList catches={catches}/> : <div class="state compact-state"><h2>Уловов пока нет</h2><p>Для этой точки нет одобренных наблюдений.</p></div>}
|
||||
</div>
|
||||
<aside><span class="eyebrow">Оценка за 24 часа</span>{activity && level ? <><h2>{activity.activity_score} / 100</h2><strong data-activity-level={level.short}>{level.description}</strong><div class="source-strip">{activity.sources.map(source => <SourceBadge source={source}/>)}</div><p>{activity.explanation}</p><p class="note">Основано на {activity.catches} {plural(activity.catches, ["наблюдении", "наблюдениях", "наблюдениях"])} от {activity.unique_players} {plural(activity.unique_players, ["игрока", "игроков", "игроков"])}. Уверенность: {activity.confidence_score}%.</p></> : <p>За последние 24 часа данных для расчёта нет.</p>}<span class="eyebrow">Лучшие приманки</span>{spot.top_baits.length ? <ol>{spot.top_baits.map(name => <li>{name}</li>)}</ol> : <p>Недостаточно данных.</p>}<p class="note">Учитываются только одобренные наблюдения.</p></aside>
|
||||
</section>
|
||||
</>}
|
||||
</Layout>
|
||||
|
||||
@@ -124,6 +124,7 @@
|
||||
### Графические элементы и объяснение данных
|
||||
|
||||
- [x] Не показывать дублирующий sidebar лидера при единственной найденной точке (7 сентября 2026).
|
||||
- [x] Показывать у каждого улова на detail-странице источник, относительную свежесть и точную дату в подсказке (7 сентября 2026).
|
||||
- [x] Подготовить фирменный знак «крючок + поплавок + координатная сетка» и согласованный набор favicon/app icons (7 сентября 2026).
|
||||
- [x] Добавить честный координатный радар без имитации отсутствующей карты на страницу точки (7 сентября 2026).
|
||||
- [x] Добавить временную шкалу активности 72 часа с 12-часовым шагом в виде лески с поплавками (7 сентября 2026).
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@
|
||||
- [x] Заменить slug-input фильтрами из справочников и добавить сброс.
|
||||
- [x] Сделать мобильную выдачу рекордов самодостаточной с подписями значений.
|
||||
- [x] Убрать CLI-команду из публичного empty state.
|
||||
- [ ] Добавить к уловам detail-страницы дату/свежесть и происхождение, если API их отдаёт.
|
||||
- [x] Добавить к уловам detail-страницы семантическую дату, относительную свежесть и происхождение (7 сентября 2026).
|
||||
- [x] Устранить дублирование карточки и sidebar на малой выборке: блок лидера показывается только при двух и более результатах (7 сентября 2026).
|
||||
|
||||
Критерий: фильтрация не требует технических терминов; каждая мобильная запись читается без заголовка таблицы; источник и свежесть понятны.
|
||||
|
||||
Reference in New Issue
Block a user