37 lines
3.1 KiB
Plaintext
37 lines
3.1 KiB
Plaintext
---
|
|
import SourceBadge from "./SourceBadge.astro";
|
|
import SectionHeading from "./SectionHeading.astro";
|
|
import { ago, kg, type PublicObservation } from "../lib/api";
|
|
const { signals } = Astro.props as { signals: PublicObservation[] };
|
|
const groups = [...signals.reduce((map, signal) => {
|
|
// Similar fields do not establish that two sources describe the same catch.
|
|
const key = JSON.stringify([signal.source_system, signal.id]);
|
|
const current = map.get(key);
|
|
if (!current) map.set(key, { ...signal, observations: [signal] });
|
|
else {
|
|
current.observations.push(signal);
|
|
current.last_seen_at = new Date(signal.last_seen_at) > new Date(current.last_seen_at) ? signal.last_seen_at : current.last_seen_at;
|
|
current.missing_fields = [...new Set([...current.missing_fields, ...signal.missing_fields])];
|
|
if (signal.quality === "incomplete") current.quality = "incomplete";
|
|
}
|
|
return map;
|
|
}, new Map<string, PublicObservation & { observations: PublicObservation[] }>()).values()];
|
|
---
|
|
<section class="signal-section content-grid" aria-labelledby="signals-title">
|
|
<SectionHeading editorial eyebrow="Сырые данные источников" title="Полевые сигналы" id="signals-title" description="Показываем сразу, даже если часть полей отсутствует. Эти карточки не участвуют в расчёте активности до полного подтверждения." count={`${signals.length} сигналов · ${groups.length} сюжетов`} />
|
|
<div class="signal-grid">{groups.map(signal => {
|
|
const provenance = [...new Map(signal.observations.map(item => [`${item.source_system}|${item.source_url}`, item])).values()];
|
|
return <article class="signal-card" data-quality={signal.quality}>
|
|
<div class="signal-card__top"><div class="source-strip">{provenance.map(item => <SourceBadge source={item.source_system} href={item.source_url}/>)}</div><span class="quality-chip"><i>{signal.quality === "incomplete" ? "!" : "?"}</i> {signal.quality === "incomplete" ? "Неполные данные" : "Ждёт проверки"}</span></div>
|
|
<h3>{signal.fish_name}</h3><p>{signal.waterbody_name}</p>
|
|
<dl><div><dt>Точка</dt><dd>{signal.x == null || signal.y == null ? "не указана" : `${signal.x}:${signal.y}`}</dd></div><div><dt>Вес</dt><dd>{signal.weight_g == null ? "не указан" : kg(signal.weight_g)}</dd></div><div><dt>Получено</dt><dd>{ago(signal.last_seen_at)}</dd></div></dl>
|
|
{signal.observations.length > 1 && <p class="repeat-note">Совпали {signal.observations.length} наблюдения · все источники сохранены</p>}
|
|
<p class="missing-note"><span>{signal.missing_fields.length ? "Не хватает:" : "Статус:"}</span> {signal.missing_fields.join(", ") || "проверяется соответствие справочнику"}</p>
|
|
</article>;
|
|
})}</div>
|
|
</section>
|
|
<style>
|
|
.repeat-note{position:relative;z-index:1;margin:-8px 0 10px!important;padding:6px 9px;border-left:2px solid #6b8d74;color:#496357!important;font-size:10px!important}
|
|
.signal-card__top .source-strip{margin:0}
|
|
</style>
|