diff --git a/tradein-mvp/frontend/src/app/v2/page.tsx b/tradein-mvp/frontend/src/app/v2/page.tsx index 1bc3617e..4917d454 100644 --- a/tradein-mvp/frontend/src/app/v2/page.tsx +++ b/tradein-mvp/frontend/src/app/v2/page.tsx @@ -289,12 +289,63 @@ function EmptyResultPanel() { ); } -function InsufficientPanel() { +// Named predicate for the "no price at all" state — deliberately NOT +// `!estimate.median_price_rub` (a bare zero/price check): that would also +// misfire on a still-loading `estimate` or on legitimate falsy-but-present +// values. The one reliable signal is the backend's own `insufficient_data` +// flag, which is defined (see AggregatedEstimate in types/trade-in.ts / +// backend @computed_field) to mean "this IS a successful response, but +// median_price_rub <= 0 — there was nothing to price with". Only fires on a +// present, successfully-returned estimate. +function isInsufficientEstimate(estimate: AggregatedEstimate | null): boolean { + return estimate != null && estimate.insufficient_data === true; +} + +// Карточка состояния «цены нет». Показывает объяснение, которое прислал сам +// бэкенд (confidence_explanation — текст написан специально под это состояние), +// и адрес, который геокодер всё-таки нашёл: target_address и координаты +// заполнены даже когда цены нет. Плюс понятное следующее действие. +// Цена, диапазон, «−N% к рынку» и аналоги здесь не рендерятся вовсе — на +// проводе они 0/null, и показать их значило бы выдать сломанный экран «0 ₽» +// вместо честного «дом нашли, цену — нет». +function InsufficientPanel({ + estimate, + onNew, +}: { + estimate: AggregatedEstimate; + onNew: () => void; +}) { return ( + title="НЕ ХВАТАЕТ ДАННЫХ ДЛЯ ОЦЕНКИ" + body={ + estimate.confidence_explanation ?? + "По этому адресу не нашлось достаточного числа аналогов и сделок для надёжной оценки. Уточните адрес или параметры квартиры." + } + > + {estimate.target_address && ( +
+ Найденный адрес: {estimate.target_address} +
+ )} +
+ +
+
); } @@ -588,7 +639,7 @@ export default function TradeInV2Page() { // existing Серов estimates were already stuck in this state). See // mapResultPanel's `dealsOnlyPrice` for how the result panel discloses the // deals-only source instead of mislabelling it as "по объявлениям". - const insufficient = estimate != null && estimate.insufficient_data; + const insufficient = isInsufficientEstimate(estimate); const apiError = mutation.error?.message ?? null; // M4: estimate-dependent meta/controls (the «ДЕЙСТВИТЕЛЕН ДО» validity line, // «КАК РАССЧИТАНО», PDF) only render once there is a real, sufficient estimate. @@ -830,7 +881,7 @@ export default function TradeInV2Page() { ); } else if (estimate && insufficient) { - middleContent = ; + middleContent = ; } else { middleContent = ; }