"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 */}
ЦЕНА × СРОК ПРОДАЖИ
аналоги
сделки
объект
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 ?? "—"}
);
}