Карточка «не хватает данных» вместо статичной заглушки (#3539)
All checks were successful
Deploy Trade-In / changes (push) Successful in 18s
Deploy Trade-In / test (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 2m27s
Deploy Trade-In / deploy (push) Successful in 1m3s
Deploy Trade-In / deploy-status (push) Successful in 2s
Deploy Trade-In / perimeter-smoke (push) Successful in 1m42s

This commit is contained in:
lekss361 2026-09-16 17:01:54 +00:00
parent 31b207be93
commit 576dba9b24

View file

@ -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 (
<PlaceholderPanel
title="НЕДОСТАТОЧНО ДАННЫХ"
body="По этому адресу не нашлось достаточного числа аналогов и сделок для надёжной оценки. Уточните адрес или параметры квартиры."
/>
title="НЕ ХВАТАЕТ ДАННЫХ ДЛЯ ОЦЕНКИ"
body={
estimate.confidence_explanation ??
"По этому адресу не нашлось достаточного числа аналогов и сделок для надёжной оценки. Уточните адрес или параметры квартиры."
}
>
{estimate.target_address && (
<div
style={{
fontSize: 12,
color: tokens.ink,
maxWidth: 360,
lineHeight: 1.5,
}}
>
Найденный адрес: {estimate.target_address}
</div>
)}
<div style={{ display: "flex", gap: 10, marginTop: 4 }}>
<button
type="button"
className="v2-retry-btn"
onClick={onNew}
style={retryBtnStyle}
>
УТОЧНИТЬ АДРЕС
</button>
</div>
</PlaceholderPanel>
);
}
@ -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() {
</div>
);
} else if (estimate && insufficient) {
middleContent = <InsufficientPanel />;
middleContent = <InsufficientPanel estimate={estimate} onNew={handleNew} />;
} else {
middleContent = <EmptyResultPanel />;
}