diff --git a/tradein-mvp/frontend/src/components/trade-in/HeroSummary.tsx b/tradein-mvp/frontend/src/components/trade-in/HeroSummary.tsx index 1b5fa8c0..f0fc7b62 100644 --- a/tradein-mvp/frontend/src/components/trade-in/HeroSummary.tsx +++ b/tradein-mvp/frontend/src/components/trade-in/HeroSummary.tsx @@ -390,10 +390,18 @@ export function HeroSummary({ estimate, input, onResubmit, isResubmitting = fals )} - {/* ── Две равноправные hero-цены: «Выставить» (asking) + «Получить» (sold). ── - При hasSold=false (старые оценки / пустая ratio) — одиночный layout - только с «Выставить». Empty-state при insufficientData. */} -
+ {/* ── §1.1 «Одна цифра + честный разброс»: expected_sold — цифра-герой + («оценка» / ожидаемая цена продажи), median — вторичный ориентир + «рекомендованная цена в объявлении», НЕ конкурирует за внимание. + При hasSold=false (старые оценки / пустая ratio) median остаётся + единственной честной цифрой. Empty-state при insufficientData. */} +
{insufficientData ? (
Оценка недоступна
@@ -404,11 +412,50 @@ export function HeroSummary({ estimate, input, onResubmit, isResubmitting = fals снова.

- ) : ( + ) : hasSold ? ( <> - {/* Левая ячейка: «Выставить в объявлении» */} + {/* Цифра-герой: «оценка» = ожидаемая цена продажи (expected_sold) */} +
+
+ Оценка · ожидаемая цена продажи + {showDiscount && ( + + −{discountPct}% к объявлению + + )} +
+
{formatMln(sold as number)} ₽
+ {typeof soldLo === "number" && typeof soldHi === "number" && ( +
+ рыночный разброс: {formatMln(soldLo)} – {formatMln(soldHi)} ₽ +
+ )} + {typeof soldPerM2 === "number" && ( +
+ {soldPerM2.toLocaleString("ru-RU")} ₽/м² +
+ )} +

+ Диапазон отражает разброс цен по рынку, а не погрешность оценки. +

+ {estimate.ratio_basis === "global_fallback" ? ( +
+ Ср.-городской коэффициент (мало сделок в сегменте) +
+ ) : estimate.ratio_basis === "per_rooms" ? ( +
+ Коэффициент по сделкам той же планировки +
+ ) : null} +
+ + {/* Вторичный ориентир: рекомендованная цена в объявлении (median) */}
-
Выставить в объявлении
+
Рекомендованная цена в объявлении
{formatMln(m)} ₽
{formatMln(lo)} – {formatMln(hi)} ₽ @@ -417,44 +464,18 @@ export function HeroSummary({ estimate, input, onResubmit, isResubmitting = fals {estimate.median_price_per_m2.toLocaleString("ru-RU")} ₽/м²
- - {/* Правая ячейка: «Получить (ожидаемая сделка)» — только при hasSold */} - {hasSold && ( -
-
- Получить (ожидаемая сделка) - {showDiscount && ( - - −{discountPct}% - - )} -
-
{formatMln(sold as number)} ₽
- {typeof soldLo === "number" && typeof soldHi === "number" && ( -
- {formatMln(soldLo)} – {formatMln(soldHi)} ₽ -
- )} - {typeof soldPerM2 === "number" && ( -
- {soldPerM2.toLocaleString("ru-RU")} ₽/м² -
- )} - {estimate.ratio_basis === "global_fallback" ? ( -
- Ср.-городской коэффициент (мало сделок в сегменте) -
- ) : estimate.ratio_basis === "per_rooms" ? ( -
- Коэффициент по сделкам той же планировки -
- ) : null} -
- )} + ) : ( +
+
Рекомендованная цена в объявлении
+
{formatMln(m)} ₽
+
+ {formatMln(lo)} – {formatMln(hi)} ₽ +
+
+ {estimate.median_price_per_m2.toLocaleString("ru-RU")} ₽/м² +
+
)}
diff --git a/tradein-mvp/frontend/src/components/trade-in/OfferCard.tsx b/tradein-mvp/frontend/src/components/trade-in/OfferCard.tsx index 57afae91..0bc96998 100644 --- a/tradein-mvp/frontend/src/components/trade-in/OfferCard.tsx +++ b/tradein-mvp/frontend/src/components/trade-in/OfferCard.tsx @@ -58,7 +58,7 @@ export function OfferCard({ estimate, brandSlug }: Props) {
- Оценка по рынку:{" "} + Цена в объявлении:{" "} {estimate.insufficient_data ? "—" : `${formatMln(estimate.median_price_rub)} ₽`} 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 36bfb28f..9af42350 100644 --- a/tradein-mvp/frontend/src/components/trade-in/trade-in.css +++ b/tradein-mvp/frontend/src/components/trade-in/trade-in.css @@ -756,6 +756,35 @@ font-style: italic; } + /* ── §1.1 «Одна цифра-герой»: expected_sold доминирует, median — вторичный + ориентир (не конкурирует за внимание). Применяется только когда есть + expected_sold (класс .hero-duo--sold-primary). ── */ + .hero-duo--sold-primary { + grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); + align-items: stretch; + } + /* Цифра-герой (expected_sold) — крупнее, чем вторичный median. */ + .hero-duo--sold-primary .hero-duo__cell[data-role=sold] .hero-duo__value { + font-size: 34px; + } + /* Вторичный median-ориентир — приглушённый фон + меньший кегль числа. */ + .hero-duo--sold-primary .hero-duo__cell[data-role=asking] { + background: var(--surface-1, var(--bg)); + } + .hero-duo--sold-primary .hero-duo__cell[data-role=asking] .hero-duo__value { + font-size: 20px; + font-weight: 600; + color: var(--fg-2); + } + /* Честное пояснение к разбросу — «не погрешность оценки». */ + .hero-duo__hint { + margin: 6px 0 0; + font-size: 11px; + line-height: 1.45; + color: var(--muted); + max-width: 44ch; + } + /* ── Компактный ряд бенчмарков под hero-duo ── */ .hero-benchmarks { display: flex; @@ -1405,6 +1434,7 @@ .form-card { position: static; max-height: none; } .hero-top { grid-template-columns: 1fr; } .hero-duo { grid-template-columns: 1fr; } + .hero-duo--sold-primary { grid-template-columns: 1fr; } .hero-prices { grid-template-columns: 1fr; } .hero-photo { aspect-ratio: 16/9; } .progress-list { grid-template-columns: 1fr; } diff --git a/tradein-mvp/frontend/src/components/trade-in/v2/ResultPanel.tsx b/tradein-mvp/frontend/src/components/trade-in/v2/ResultPanel.tsx index be303949..47b04928 100644 --- a/tradein-mvp/frontend/src/components/trade-in/v2/ResultPanel.tsx +++ b/tradein-mvp/frontend/src/components/trade-in/v2/ResultPanel.tsx @@ -432,6 +432,20 @@ export default function ResultPanel({ })}
+ {/* §1.2 честный разброс: диапазоны цен = рыночный спред, НЕ погрешность + оценки. Ставим сразу под ценовыми карточками, чтобы широкий диапазон + не читался как «неуверенность расчёта». */} +
+ Диапазоны показывают разброс цен на рынке, а не погрешность оценки. +
+ {/* ranges + radar */}