From 9af88ebaa54b5929ab7046147a29aa3785760b54 Mon Sep 17 00:00:00 2001 From: bot-frontend Date: Sat, 30 May 2026 18:33:26 +0300 Subject: [PATCH] =?UTF-8?q?feat(tradein):=20HeroSummary=20empty-state=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=20insufficient=5Fdata?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit При пустой оценке (median<=0, backend #697) headline показывал «0,00 млн ₽». Теперь рендерим явный empty-state «Недостаточно данных для оценки» вместо нулевого числа. Добавлено поле insufficient_data в AggregatedEstimate. Refs #740 --- .../src/components/trade-in/HeroSummary.tsx | 71 ++++++++++++------- .../src/components/trade-in/trade-in.css | 6 ++ tradein-mvp/frontend/src/types/trade-in.ts | 1 + 3 files changed, 51 insertions(+), 27 deletions(-) diff --git a/tradein-mvp/frontend/src/components/trade-in/HeroSummary.tsx b/tradein-mvp/frontend/src/components/trade-in/HeroSummary.tsx index 617524de..4cf8d274 100644 --- a/tradein-mvp/frontend/src/components/trade-in/HeroSummary.tsx +++ b/tradein-mvp/frontend/src/components/trade-in/HeroSummary.tsx @@ -95,6 +95,9 @@ export function HeroSummary({ estimate, input, onResubmit, isResubmitting = fals const m = estimate.median_price_rub; const lo = estimate.range_low_rub; const hi = estimate.range_high_rub; + // Пустая оценка (backend #697): нет аналогов/сделок → median<=0. Вместо + // «0,00 млн ₽» в headline показываем явный empty-state. + const insufficientData = estimate.insufficient_data || m <= 0; // ── Ожидаемая цена продажи (S3). Рисуем второй блок только при валидном числе: // null/undefined/0 (старые оценки или пустая ratio-таблица) → одиночный layout. ── @@ -294,33 +297,47 @@ export function HeroSummary({ estimate, input, onResubmit, isResubmitting = fals запроса, с −X%). Без него (старые оценки / пустая ratio) — headline = asking-медиана, чтобы блок не оставался без главного числа. */}
-
- {hasSold ? "Ожидаемая цена сделки" : "Рекомендованная цена"} - {showDiscount && ( - - −{discountPct}% к объявлению - - )} -
-
- {formatMln(hasSold ? (sold as number) : m)} ₽ -
- {hasSold && typeof soldLo === "number" && typeof soldHi === "number" && ( -
- ожидаемый диапазон {formatMln(soldLo)} – {formatMln(soldHi)} ₽ - {typeof soldPerM2 === "number" - ? ` · ${soldPerM2.toLocaleString("ru-RU")} ₽/м²` - : ""} -
- )} - {hasSold && ( -

- Реальные сделки проходят на 5–12% ниже цен в объявлениях — это - ожидаемая цена сделки по данным ДКП Росреестра, а не цена запроса. -

+ {insufficientData ? ( + <> +
Оценка недоступна
+
Недостаточно данных для оценки
+

+ Рядом не нашлось достаточно сопоставимых объявлений и сделок, чтобы + рассчитать цену. Уточните адрес или параметры объекта и попробуйте + снова. +

+ + ) : ( + <> +
+ {hasSold ? "Ожидаемая цена сделки" : "Рекомендованная цена"} + {showDiscount && ( + + −{discountPct}% к объявлению + + )} +
+
+ {formatMln(hasSold ? (sold as number) : m)} ₽ +
+ {hasSold && typeof soldLo === "number" && typeof soldHi === "number" && ( +
+ ожидаемый диапазон {formatMln(soldLo)} – {formatMln(soldHi)} ₽ + {typeof soldPerM2 === "number" + ? ` · ${soldPerM2.toLocaleString("ru-RU")} ₽/м²` + : ""} +
+ )} + {hasSold && ( +

+ Реальные сделки проходят на 5–12% ниже цен в объявлениях — это + ожидаемая цена сделки по данным ДКП Росреестра, а не цена запроса. +

+ )} + )}
diff --git a/tradein-mvp/frontend/src/components/trade-in/trade-in.css b/tradein-mvp/frontend/src/components/trade-in/trade-in.css index ff0da9a1..c963192f 100644 --- a/tradein-mvp/frontend/src/components/trade-in/trade-in.css +++ b/tradein-mvp/frontend/src/components/trade-in/trade-in.css @@ -705,6 +705,12 @@ letter-spacing: -0.025em; color: var(--fg); } + .headline-value-empty { + font-size: 22px; + font-weight: 600; + line-height: 1.27; + color: var(--fg); + } .headline-range { font-size: 12px; color: var(--muted); diff --git a/tradein-mvp/frontend/src/types/trade-in.ts b/tradein-mvp/frontend/src/types/trade-in.ts index 5d0e66cb..9b75662e 100644 --- a/tradein-mvp/frontend/src/types/trade-in.ts +++ b/tradein-mvp/frontend/src/types/trade-in.ts @@ -123,6 +123,7 @@ export interface AggregatedEstimate { confidence: ConfidenceLevel; confidence_explanation: string | null; n_analogs: number; + insufficient_data: boolean; // backend #697: true когда median_price_rub <= 0 (нет данных) period_months: number; // 24 analogs: AnalogLot[]; // top 5-10 actual_deals: AnalogLot[]; // last 12 mo