"use client"; /** * DistributionCard — гистограмма распределения ₽/м² по аналогам с маркером * «ваша квартира здесь» на median_price_per_m2 оценки. Reuses recharts (как * PriceHistoryChart.tsx) — никаких новых зависимостей. */ import { useMemo } from "react"; import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid, ReferenceLine, Cell, } from "recharts"; import type { AggregatedEstimate } from "@/types/trade-in"; interface Props { estimate: AggregatedEstimate; } const BIN_COUNT = 12; const fmtK = (n: number) => `${Math.round(n / 1000)}k`; interface Bin { /** Левая граница бина, ₽/м². */ start: number; /** Правая граница бина, ₽/м². */ end: number; /** Центр бина (X-координата столбца). */ mid: number; count: number; /** True если в этот бин попадает median_price_per_m2 оценки. */ isSubject: boolean; } export function DistributionCard({ estimate }: Props) { const subject = estimate.median_price_per_m2; const bins = useMemo(() => { const values = estimate.analogs .map((l) => l.price_per_m2) .filter((v) => typeof v === "number" && v > 0); if (values.length < 3) return []; const min = Math.min(...values, subject); const max = Math.max(...values, subject); if (max <= min) return []; const width = (max - min) / BIN_COUNT; const out: Bin[] = Array.from({ length: BIN_COUNT }, (_, i) => { const start = min + i * width; const end = i === BIN_COUNT - 1 ? max : start + width; return { start, end, mid: (start + end) / 2, count: 0, isSubject: false }; }); for (const v of values) { let idx = Math.floor((v - min) / width); if (idx >= BIN_COUNT) idx = BIN_COUNT - 1; if (idx < 0) idx = 0; out[idx].count += 1; } let subjIdx = Math.floor((subject - min) / width); if (subjIdx >= BIN_COUNT) subjIdx = BIN_COUNT - 1; if (subjIdx < 0) subjIdx = 0; out[subjIdx].isSubject = true; return out; }, [estimate.analogs, subject]); // Empty state: меньше 3 валидных аналогов — распределение не показываем. if (bins.length === 0) return null; const n = estimate.analogs.filter((l) => l.price_per_m2 > 0).length; return (
Аналитика · Распределение

Где ваша квартира в локальном рынке

По {n} аналогам
₽/м² · гистограмма
{/* #835: декоративный чарт — aria-hidden (данные в тексте/легенде карточки). */}
Ваша оценка:{" "} {subject.toLocaleString("ru-RU")} ₽/м² · оранжевый столбец — ваш диапазон
); }