"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, body2, muted, muted2, muted3, line, line2, lineSoft, bracket, barLo, barMid, track, success, hint, disabledBorder, disabledValue, disabledLabel, infoSoftBg, 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) => { // Price hierarchy (audit H2). The three cards are NOT equal weight: // ci 0 — РЕКОМЕНДОВАННАЯ ЦЕНА В ОБЪЯВЛЕНИИ (market asking, baseline) // ci 1 — ОЖИДАЕМАЯ ЦЕНА СДЕЛКИ (the real answer → headline) // ci 2 — ДКП · РОСРЕЕСТР (registry reference, ~−43% → demoted) // The headline gets an accent border + tint + glow + larger accent // value so the user can't confuse it with the registry figure; the // registry card is demoted to a softer, smaller, muted "СПРАВОЧНО" // tile. All three values stay (honesty); wiring (mapResultPanel) is // untouched — emphasis is presentation only. const isHeadline = ci === 1; const isRegistry = ci === 2; return (
{lines(card.title)}
{isRegistry && (
СПРАВОЧНО
)}
{card.value} {card.unit}
{card.range}
{card.ppm}
{card.note && (
{card.note}
)} {card.bars ? (
{card.bars.map((h, bi) => ( ))}
) : ( <>
{card.delta} {card.deltaLabel}
)}
); })}
{/* §1.2 честный разброс: диапазоны цен = рыночный спред, НЕ погрешность оценки. Ставим сразу под ценовыми карточками, чтобы широкий диапазон не читался как «неуверенность расчёта». */}
Диапазоны показывают разброс цен на рынке, а не погрешность оценки.
{/* 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}
{/* 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 */}
ИСТОЧНИКИ ДАННЫХ
{data.sources.map((s, si) => s.active ? (
{sourceName(s.name)}
{s.count}
{s.label}
) : (
{s.name}
{s.count}
{s.label}
), )}
При недоступности источника частичный результат не блокируется {data.meta.builtOn ?? "—"}
); }