diff --git a/apps/web/src/pages/spots/[id].astro b/apps/web/src/pages/spots/[id].astro index fe61b57..65946ca 100644 --- a/apps/web/src/pages/spots/[id].astro +++ b/apps/web/src/pages/spots/[id].astro @@ -31,6 +31,8 @@ const level = activity ? activityLevel(activity.activity_score) : null; const methodLabels: Record = { spinning: "Спиннинг", bottom: "Донная", float: "Поплавочная" }; const method = catches.map(item => item.fishing_method).find(Boolean) ?? ""; const retrieve = catches.map(item => item.retrieve_method).find(Boolean) ?? ""; +const confirmedMethods = [...new Set(catches.map(item => item.fishing_method).filter((value): value is string => Boolean(value)))].map(value => methodLabels[value] ?? value); +const confirmedRetrieves = [...new Set(catches.map(item => item.retrieve_method).filter((value): value is string => Boolean(value)))]; const risk = activity ? activity.catches < 3 ? "Малая выборка" : activity.confidence_score < 50 ? "Низкая уверенность" : "Подтверждено" : "Нет оценки"; const spotDescription = spot ? `Свежие уловы и активность на точке ${spot.x}:${spot.y}, ${spot.waterbody}: рыба, вес, приманки и источники данных.` : "Данные точки ловли Russian Fishing 4."; const breadcrumbs = spot ? { "@context": "https://schema.org", "@type": "BreadcrumbList", itemListElement: [ @@ -50,16 +52,25 @@ const breadcrumbs = spot ? { "@context": "https://schema.org", "@type": "Breadcr

Последние уловы

{catches.length ? :

Уловов пока нет

Для этой точки нет одобренных наблюдений.

} - + } + diff --git a/apps/web/src/pages/waterbodies/[slug]/[fish].astro b/apps/web/src/pages/waterbodies/[slug]/[fish].astro index e663c43..afe8292 100644 --- a/apps/web/src/pages/waterbodies/[slug]/[fish].astro +++ b/apps/web/src/pages/waterbodies/[slug]/[fish].astro @@ -1,16 +1,20 @@ --- import ActivityCard from "../../../components/ActivityCard.astro"; +import ActivityPeriodFilter from "../../../components/ActivityPeriodFilter.astro"; import AtlasBreadcrumbs from "../../../components/AtlasBreadcrumbs.astro"; import PageHero from "../../../components/PageHero.astro"; import StatePanel from "../../../components/StatePanel.astro"; import Layout from "../../../layouts/Layout.astro"; import { api, plural, type Activity, type DictionaryItem, type PaginatedActivity } from "../../../lib/api"; const { slug, fish: fishSlug } = Astro.params; +const requestedHours = Astro.url.searchParams.get("hours") ?? "72"; +const hours = ["24", "72", "168"].includes(requestedHours) ? requestedHours : "72"; +const periodLabel = { "24": "24 часа", "72": "72 часа", "168": "7 дней" }[hours]; let water: DictionaryItem | undefined, fish: DictionaryItem | undefined, items: Activity[] = [], unavailable = false; try { const [waters, fishes] = await Promise.all([api("/api/v1/waterbodies?limit=500"), api("/api/v1/fishes?limit=500")]); water = waters.find(item => item.slug === slug); fish = fishes.find(item => item.slug === fishSlug); - if (water && fish) { const paginated = await api(`/api/v1/activity?hours=72&waterbody=${encodeURIComponent(water.slug)}&fish=${encodeURIComponent(fish.slug)}&limit=100`); items = paginated.items; } + if (water && fish) { const paginated = await api(`/api/v1/activity?hours=${hours}&waterbody=${encodeURIComponent(water.slug)}&fish=${encodeURIComponent(fish.slug)}&limit=100`); items = paginated.items; } } catch { unavailable = true; } if (unavailable) { Astro.response.status = 503; @@ -23,6 +27,7 @@ const schema = valid ? { "@type":"CollectionPage", name:`${fish!.name_ru} на $ --- - - {unavailable ? : !valid ? :
{items.length ? items.map(item => ) : }
} + + {valid && } + {unavailable ? : !valid ? :
{items.length ? items.map(item => ) : }
}
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 93a1629..22bde0f 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -97,7 +97,7 @@ - [ ] **U03 · Evidence/trust card.** Общий evidence-контракт используется на activity-карточках, detail точки, водоёма и карточках снастей: freshness с текстовым `Свежо`/`Устарело`, явный период расчёта, completeness, confidence/статус, source badges и доступные доменные поля; при выборке меньше 3 явно показано отдельное состояние `Недостаточно данных`, ограниченный источник не смешивается с неполными полями, а явно переданные provenance-конфликты видны текстом. Источники координат доступны в раскрываемом блоке паспорта, тогда как точность остаётся видимой в основном ответе. Targeted E2E и API-тест проверяют публичные пути, период, 48-часовой порог и конфликт источников; единый `passportDisplayStatus` дополнительно закрепляет приоритет `conflict` → `stale` → исходный статус unit-регрессией. Все публичные provenance links проходят credential-free HTTP(S) allowlist. - [ ] **U04 · List/map и progressive disclosure.** List остаётся честным базовым режимом: фильтры, сортировка, URL-состояние и evidence-карточки уже работают без имитации координатной карты. Источники координат в activity/spot evidence-паспорте и многокомпонентный комплект снастей в списке уловов раскрываются по запросу, не скрывая основную точность, статус и источник; canonical-компоненты сохраняют переходы в каталог. Режим `view=map` использует единый URL-контракт, на mobile открывается отдельным действием и прямо маркирован как координатная схема, пока геометрия водоёма не подтверждена. Вторичные raw/provenance-поля не исчезают и раскрываются по запросу. - [ ] **U05 · Mobile-first и сохранённый план рыбалки.** `/plan` поддерживает список до 5 уникальных локальных вариантов, удаление, очистку, переход к точке, print/PDF и восстановление из shareable URL; кнопка «Поделиться планом» использует native share или clipboard fallback, отдельное копирование ссылки и явно сообщает об успехе, отмене, пустом плане или повреждённой share-ссылке. Detail-кнопка сохраняет данные с `aria-pressed` и восстанавливается после reload. Print/mobile-контракт проверяет 320/390 px, лимит импорта и отсутствие горизонтального overflow; импорт дедуплицирует точки, нормализует длины локальных текстовых полей, принимает confidence только в диапазоне `0..100` и не теряет реальное значение `0`, а карточка плана сравнивает подтверждённые метод/проводку и явно показывает риск по выборке/уверенности. -- [ ] **U06 · Контентная и визуальная иерархия.** Для detail точки действие «Что взять» выделено отдельным заголовком, provenance и качество собраны в общем паспорте данных, а подтверждённые метод/проводка видны рядом с последними уловами; статусы дополнительно передаются текстом, малая выборка не маркируется как готовая рекомендация, а дублирующие catches/players/confidence убраны из вторичной колонки activity-карточки. Mobile route-contract проверяет первый ответ и действие на home, spot, waterbody, plan и tackle, а task regression требует source/evidence-поля; остаётся ручной review визуальных деталей по [ux-task-review-2026-09-21.md](ux-task-review-2026-09-21.md). +- [ ] **U06 · Контентная и визуальная иерархия.** Для detail точки действие «Что взять» выделено отдельным заголовком, provenance и качество собраны в общем паспорте данных, а подтверждённые метод/проводка теперь вынесены в отдельный блок рядом с последними уловами; статусы дополнительно передаются текстом, малая выборка не маркируется как готовая рекомендация, а дублирующие catches/players/confidence убраны из вторичной колонки activity-карточки. Копирование координат имеет Clipboard API fallback. Mobile route-contract проверяет первый ответ и действие на home, spot, waterbody, plan и tackle, а task regression требует source/evidence-поля; остаётся ручной review визуальных деталей по [ux-task-review-2026-09-21.md](ux-task-review-2026-09-21.md). - [ ] **U07 · UX-приёмка и измерения.** Playwright-контракты уже покрывают query journey, сквозной task journey, evidence states, saved plan, share/print, accessibility и visual matrix; unit-контракт добавил детерминированные stale- и blocked-подписи, а insufficient-data теперь отделён от incomplete в публичном паспорте. `/status` явно сообщает, что при ограничении источника опубликованные данные сохранены. Visual-matrix regression теперь проверяет `252` комбинации, включая valid/error spot, plan и tackle analytics; representative review зафиксирован в `docs/ux-reference-review-2026-09-21.md`. Локальный Lighthouse baseline обновлён: performance 98 и accessibility 100. Остаётся получить production Lighthouse/CLS/INP/time-to-first-useful-answer после пилота и принять решение о долговременном хранении screenshots. - [ ] **Q01 · Документы источников — реестр готов, нужны первичные подтверждения.** Создан единый production-gate с атрибуцией, общим лимитом 30 минут, хранением и процедурой отзыва для RF4DB, RF4-STAT, RF4MAP, RF4 Posts и официального RF4. До открытой публикации приложить устойчивые ссылки/копии первичных разрешений, контакты, даты и отдельно подтвердить право на изображения; пустое поле блокирует соответствующий источник.