/** * FutureSupply — 6.6 «Будущее предложение и конкуренты». Renders * §9.7 forward-looking competitors (report.future_market.future_competitors): * one row per forecast competitor — ЖК / проект · класс · квартир · дистанция · * темп поглощения. Distinct from the market_now competitors block: these are the * top-N projects ranked by `relevance_weight` at the horizon that will compete * for the same demand. * * The prototype's «Срок сдачи» column has NO backing field in the §22 report, so * it is dropped in favour of the real `velocity_per_month` (темп поглощения) — * every value traces to a real forecast field. Rows are sorted by distance asc * (closest competitors first). Nullable fields render «нет данных», never a * fabricated 0. Empty/absent list → honest empty-state, NOT a hidden section. */ import styles from "@/app/site-finder/analysis/[cad]/ptica/ptica.module.css"; import type { FutureCompetitor } from "@/types/forecast"; import { fmtInt, fmtNum } from "./scenario-helpers"; interface Props { competitors: FutureCompetitor[] | undefined; } /** Distance in metres → «445 м» / «1.20 км» (mirrors PticaMapInner.formatMeters). */ function formatMeters(m: number): string { if (m >= 1000) return `${(m / 1000).toFixed(2)} км`; return `${Math.round(m)} м`; } export function FutureSupply({ competitors }: Props) { const rows = (competitors ?? []) .slice() .sort((a, b) => a.distance_m - b.distance_m); return ( <>
| ЖК / проект | Класс | Квартир | Дистанция | Поглощение |
|---|---|---|---|---|
| {c.comm_name ?? "нет данных"} | {c.obj_class ?? "нет данных"} | {c.flats_total != null ? `${fmtInt(c.flats_total)} кв` : "нет данных"} | {formatMeters(c.distance_m)} | {`${fmtNum(c.velocity_per_month, 1)} кв/мес`} |
Конкурирующие проекты в горизонте прогноза, отранжированные по близости. Поглощение — темп продаж проекта (квартир в месяц).
> )}