feat: link tackle items to related rigs

This commit is contained in:
ik
2026-09-21 20:18:23 +07:00
parent 26ad9b5194
commit c38097767f
5 changed files with 28 additions and 5 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ export type RigComponent = { id: string; role: string; position: number; raw_val
export type Rig = { id: string; name: string; source_system: string | null; source_external_id: string | null; source_url: string | null; source_checked_at: string | null; missing_fields: string[]; components: RigComponent[] };
export type Catch = { id: string; fish: string; weight_g: number; bait: string | null; player_name: string | null; caught_at: string | null; reported_at: string; fishing_method: string | null; retrieve_method: string | null; retrieve_speed: number | null; source_system: string; source_url: string | null; tackle_components: CatchTackleComponent[] };
export type DictionaryItem = { id: string; slug: string; name_ru: string; unlock_level?: number | null; fish_species_count?: number | null; source_system?: string | null; source_external_id?: string | null; source_url?: string | null; description?: string | null; source_aliases?: string[] | null; source_fish_species?: string[] | null; source_image_urls?: string[] | null; source_point_urls?: string[] | null; source_checked_at?: string | null };
export type TackleItem = { id: string; name: string; category: string; subcategory: string | null; brand: string | null; family: string | null; unlock_level: number | null; source_system: string | null; source_external_id: string | null; source_url: string | null; source_checked_at: string | null; missing_fields: string[] };
export type TackleItem = { id: string; name: string; category: string; subcategory: string | null; brand: string | null; family: string | null; unlock_level: number | null; source_system: string | null; source_external_id: string | null; source_url: string | null; source_checked_at: string | null; missing_fields: string[]; related_rigs: { id: string; name: string; source_system: string | null; source_checked_at: string | null; component_count: number }[] };
export type PaginatedTackleItems = { items: TackleItem[]; total: number; limit: number; offset: number };
export type RigSummary = { id: string; name: string; source_system: string | null; source_external_id: string | null; source_url: string | null; source_checked_at: string | null; missing_fields: string[]; component_count: number };
export type PaginatedRigs = { items: RigSummary[]; total: number; limit: number; offset: number };
@@ -42,6 +42,18 @@ const mediaCandidates = item ? (["tackle_card", "tackle_detail"] as const).map(r
<div><dl><div><dt>Подкатегория</dt><dd>{item.subcategory ?? "Не указана"}</dd></div><div><dt>Уровень открытия</dt><dd>{item.unlock_level ?? "Не указан"}</dd></div><div><dt>Источник</dt><dd>{item.source_system ?? "Не указан"}</dd></div></dl>{missing.length > 0 && <p class="tackle-detail__missing"><strong>Не хватает:</strong> {missing.join(", ")}.</p>}{sourceHref && <a data-action="secondary" href={sourceHref} rel="noreferrer">Открыть первоисточник</a>}</div>
</section>
<DataPassport sources={item.source_system ? [item.source_system] : []} sourceUrl={item.source_url} observedAt={item.source_checked_at} completeness={missing.length ? null : 100} status={missing.length ? "incomplete" : item.source_checked_at ? "verified" : "unverified"} />
<section class="related-rigs content-grid" aria-labelledby="related-rigs-title"><div><span class="overline">Связанные сборки</span><h2 id="related-rigs-title">Где используется снасть</h2><p>Показываем только сборки, где компонент привязан к канонической карточке.</p></div>{item.related_rigs.length ? <nav class="related-rigs__list">{item.related_rigs.map(rig => <a href={`/tackle/rigs/${rig.id}`}><strong>{rig.name}</strong><span>{rig.component_count} компонентов · {rig.source_system ?? "Источник не указан"}</span></a>)}</nav> : <p>Подтверждённых связанных сборок пока нет.</p>}</section>
{mediaCandidates.length > 0 && <section class="catalog-media-grid content-grid" aria-label={`Изображения снасти: ${item.name}`}>{mediaCandidates.map(asset => <article><span class="overline">{mediaRoleLabel(asset.media_role ?? "")}</span><EntityMedia asset={asset} sourceLink /></article>)}</section>}
</>}
</Layout>
<style>
.related-rigs { display: grid; grid-template-columns: minmax(0, 1fr) minmax(280px, 1fr); gap: 24px; margin-block: 26px; }
.related-rigs h2 { margin: 6px 0 8px; }
.related-rigs p { margin: 0; color: var(--text-muted); line-height: 1.5; }
.related-rigs__list { display: grid; gap: 10px; }
.related-rigs__list a { display: grid; gap: 4px; padding: 14px 16px; border: 1px solid var(--border); border-radius: 14px; color: inherit; text-decoration: none; }
.related-rigs__list a:hover, .related-rigs__list a:focus-visible { border-color: var(--focus); }
.related-rigs__list span { color: var(--text-muted); font-size: 12px; }
@media (max-width: 720px) { .related-rigs { grid-template-columns: 1fr; } }
</style>