feat: link tackle items to related rigs
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user