"use client"; /** * ExposureCard — ликвидность: scatter цена (₽) × срок экспозиции (дней) по * аналогам. Дешевле → быстрее продаётся. Цена оценки отмечена reference-линией. * Reuses recharts (как PriceHistoryChart.tsx). Degrade gracefully: рендерим * только если ≥3 аналогов имеют непустой days_on_market. */ import { useMemo } from "react"; import { ScatterChart, Scatter, XAxis, YAxis, ZAxis, Tooltip, ResponsiveContainer, CartesianGrid, ReferenceLine, } from "recharts"; import type { AggregatedEstimate } from "@/types/trade-in"; interface Props { estimate: AggregatedEstimate; } const MIN_CHART_ANALOGS = 8; const fmtM = (n: number) => `${(n / 1_000_000).toFixed(1)}`; interface Point { price: number; // ₽ days: number; // срок экспозиции address: string; } export function ExposureCard({ estimate }: Props) { const points = useMemo( () => estimate.analogs .filter( (l) => typeof l.days_on_market === "number" && (l.days_on_market as number) > 0 && l.price_rub > 0, ) .map((l) => ({ price: l.price_rub, days: l.days_on_market as number, address: l.address, })), [estimate.analogs], ); // Degrade gracefully: <3 точек со сроком — карточку не показываем // (сводится в DistributionCard, который не требует days_on_market). if (points.length < 3) return null; const subjectPrice = estimate.median_price_rub; return (
Аналитика · Ликвидность

Цена и срок экспозиции

По {points.length} аналогам
дешевле → быстрее
{points.length < MIN_CHART_ANALOGS ? (

Недостаточно данных для графика — нужно не менее {MIN_CHART_ANALOGS} аналогов со сроком.

) : ( <> {/* #835: чарт декоративный (данные — в заголовке/легенде/тексте карточки); aria-hidden убирает recharts symbol'ы role="img" без accessible-name. */}
Каждая точка — аналог в продаже · вертикаль — ваша оценка{" "} {fmtM(subjectPrice)} млн ₽
)}
); }