"use client"; // Overlay 06 "АНАЛИТИКА ДОМА" — markup port from the МЕРА Оценка pixel design. // Accepts one composite `data` prop (default = the design fixture); page.tsx // feeds mapAnalytics(...) output. The price-history dots navigate to the // "Аналогичные объявления" overlay via onNavigate?.(2). import { useMemo, useState } from "react"; import { tokens } from "./tokens"; import { analytics as ANALYTICS_FIXTURE } from "./fixtures"; import type { Analytics, SellTimeTier } from "./types"; interface AnalyticsViewProps { data?: Analytics; onNavigate?: (i: number) => void; } // ---- price-history geometry ----------------------------------------------- // Parse the SVG polyline point strings into coordinates so the markers and // the transparent hover targets can be derived from the same source. Empty // series (graceful-degradation case) parse to [] — no dots, no hover targets. interface Pt { x: number; y: number; } function parsePoints(s: string): Pt[] { return s .trim() .split(/\s+/) .filter((pair) => pair.length > 0) .map((pair) => { const [x, y] = pair.split(",").map(Number); return { x, y }; }); } // X positions of the year axis labels (chart scaffolding, not in fixtures). const histYearX = [50, 250, 460, 670, 860]; // ---- sell-time tier tints -------------------------------------------------- // One-off decorative tints per variant (design lines 553-556). The soft border // hex (#bfe3d2 / #e3d2ad / #e6c3c3) and the rgba background fills are documented // one-off design tints with no token equivalent; the label text colour reuses // semantic tokens (success / accent / warn / danger). const tierStyles: Record< SellTimeTier["variant"], { bg: string; border: string; label: string } > = { success: { bg: "rgba(27,170,107,.07)", border: "1px solid #bfe3d2", label: tokens.success, }, accent: { bg: "rgba(46,139,255,.08)", border: `1.5px solid ${tokens.accent}`, label: tokens.accent, }, gold: { bg: "rgba(201,154,62,.09)", border: "1px solid #e3d2ad", label: tokens.warn, }, danger: { bg: "rgba(214,90,90,.07)", border: "1px solid #e6c3c3", label: tokens.danger, }, }; const cardStyle = { background: tokens.surface.w55, border: `1px solid ${tokens.line2}`, borderRadius: 8, padding: "16px 18px", } as const; export default function AnalyticsView({ data = ANALYTICS_FIXTURE, onNavigate, }: AnalyticsViewProps) { const [hoverHist, setHoverHist] = useState(false); // Price-history series + hover targets, derived from the data prop. Яндекс // only draws dots where its series diverges from Avito (последние 3 года). const avitoPts = useMemo( () => parsePoints(data.priceHistory.avito), [data.priceHistory.avito], ); const yandexPts = useMemo( () => parsePoints(data.priceHistory.yandex), [data.priceHistory.yandex], ); const yandexDots = useMemo( () => yandexPts.filter( (p, i) => !avitoPts[i] || p.x !== avitoPts[i].x || p.y !== avitoPts[i].y, ), [yandexPts, avitoPts], ); const hoverPts = useMemo( () => [...avitoPts, ...yandexDots], [avitoPts, yandexDots], ); return (
{/* header */}
Аналитика дома
{data.header.ads} · {data.header.radius}
{/* KPI row */}
{data.kpis.map((kpi, i) => (
{kpi.label}
{kpi.value} {kpi.unit ? ( <> {" "} {kpi.unit} ) : null}
{kpi.sub}
))}
{/* срок продажи в зависимости от цены */}
Срок продажи в зависимости от цены
{data.sellTimeNote}
{data.sellTime.map((tier, i) => { const v = tierStyles[tier.variant]; return (
{tier.tier}
{tier.days}
{tier.range}
{tier.count}
); })}
{/* история цен */}
История цен в этом доме
{data.priceHistory.note}
{avitoPts.length > 0 ? ( Avito ) : null} {yandexPts.length > 0 ? ( Яндекс ) : null}
{data.priceHistory.yTicks.map((t) => ( {t.label} ))} {data.priceHistory.years.map((yr, i) => ( {yr} ))} {/* white dot fill on an accent ring — documented one-off, no token (design l.568) */} Архивные объявления Avito · нажмите, чтобы открыть {avitoPts.map((p, i) => ( ))} {yandexDots.map((p, i) => ( ))} setHoverHist(true)} onMouseLeave={() => setHoverHist(false)} onClick={() => onNavigate?.(2)} > {hoverPts.map((p, i) => ( ))} {hoverHist ? ( // Dark navy tooltip surface + white text — documented one-off literals // matching the design (l.572); no token equivalent.
Архивные объявления этого дома
Нажмите, чтобы открыть список →
) : null}
{/* цена × срок продажи (детально) */}
Цена × срок продажи
Зависимость цены от срока экспозиции · {data.scatterDetail.note}
аналоги сделки объект
{data.scatterDetail.deals.map((p, i) => ( ))} {data.scatterDetail.analogs.map((p, i) => ( ))} {data.scatterDetail.yTicks.map((t) => ( {t.label} ))} {data.scatterDetail.xTicks.map((t) => ( {t.label} ))} СРОК ПРОДАЖИ, ДН ЦЕНА ПРОДАЖИ, ₽
); }