"use client"; import { Fragment } from "react"; import { tokens } from "./tokens"; import { ranges, resultCards, resultMeta, scatterMini, sources, } from "./fixtures"; import type { ResultPanelData } from "./mappers"; const { accent, ink, muted, muted2, muted3, line, line2, line3, bracket, barLo, barMid, track, success, hint, scatterDeal, disabledBorder, disabledValue, disabledLabel, surface, font, } = tokens; // Default presentation data (unwired usage): the existing design fixtures. const RESULT_FIXTURE: ResultPanelData = { cards: resultCards, meta: resultMeta, ranges, scatterMini, sources, }; interface ResultPanelProps { data?: ResultPanelData; onNavigate: (i: number) => void; } // Mini-histogram column tint: peak column = accent, its neighbours = mid, rest = lo. function barColor(i: number, bars: number[]): string { const peak = bars.indexOf(Math.max(...bars)); if (i === peak) return accent; if (i === peak - 1 || i === peak + 1) return barMid; return barLo; } // Render a string[] with
between lines (faithful to the design line breaks). function lines(parts: string[]) { return parts.map((part, i) => ( {i > 0 &&
} {part}
)); } // Split a source name on its parenthetical so the design's forced // "РОСРЕЕСТР / (ВНУТР.)" two-line break (line-height 1.3) is reproduced; plain // names without " (" stay on a single line. function sourceName(name: string) { const idx = name.indexOf(" ("); if (idx === -1) return name; return ( <> {name.slice(0, idx)}
{name.slice(idx + 1)} ); } export default function ResultPanel({ data = RESULT_FIXTURE, onNavigate, }: ResultPanelProps) { return (
{/* header */}
02 РЕЗУЛЬТАТ ОЦЕНКИ
ИСТОЧНИКОВ:{" "} {data.meta.sources} ДОСТОВЕРНОСТЬ: {data.meta.confidence} CV{" "} {data.meta.cv}
{/* result cards */}
{data.cards.map((card, ci) => (
{lines(card.title)}
{card.value} {card.unit}
{card.range}
{card.ppm}
{card.note && (
{card.note}
)} {card.bars ? (
{card.bars.map((h, bi) => ( ))}
onNavigate(card.nav)} style={{ display: "flex", alignItems: "center", gap: 4, cursor: "pointer", fontSize: 9, letterSpacing: 0.3, color: accent, whiteSpace: "nowrap", }} > Подробнее
) : ( <>
onNavigate(card.nav)} style={{ marginTop: 16, display: "flex", alignItems: "center", justifyContent: "flex-end", gap: 4, cursor: "pointer", fontSize: 9, letterSpacing: 0.3, color: accent, }} > Подробнее
{card.delta} {card.deltaLabel}
)}
))}
{/* ranges + radar */}
{/* left range — ads */}
{lines(data.ranges.ads.label)}
медиана · {data.ranges.ads.median}
{data.ranges.ads.lo} {data.ranges.ads.median} {data.ranges.ads.hi}
{/* radar — price × sell-time scatter */}
ЦЕНА × СРОК ПРОДАЖИ
{data.scatterMini.deals.map((p, i) => ( ))} {data.scatterMini.analogs.map((p, i) => ( ))} {data.scatterMini.yTicks.map((t, i) => ( {t.label} ))} {data.scatterMini.xTicks.map((t, i) => ( {t.label} ))} СРОК ПРОДАЖИ, ДН ЦЕНА ПРОДАЖИ
аналоги сделки объект
onNavigate(3)} style={{ display: "flex", alignItems: "center", gap: 4, cursor: "pointer", fontSize: 9, letterSpacing: 0.3, color: accent, marginTop: 2, }} > Подробнее
{/* right range — deals */}
{lines(data.ranges.deals.label)}
медиана · {data.ranges.deals.median}
{data.ranges.deals.lo} {data.ranges.deals.median} {data.ranges.deals.hi}
{/* sources */}
ИСТОЧНИКИ ДАННЫХ onNavigate(2)} style={{ display: "flex", alignItems: "center", gap: 4, cursor: "pointer", fontSize: 9.5, letterSpacing: 0.3, color: accent, }} > Подробнее
{data.sources.map((s, si) => s.active ? (
{sourceName(s.name)}
{s.count}
{s.label}
) : (
{s.name}
{s.count}
{s.label}
), )}
При недоступности источника частичный результат не блокируется {data.meta.builtOn ?? "—"}
); }