From a4604dd9db688d60439c9a60282a3897c7b2d362 Mon Sep 17 00:00:00 2001 From: bot-backend Date: Wed, 26 Aug 2026 12:54:32 +0500 Subject: [PATCH] =?UTF-8?q?fix(mera/ui):=20=D0=BA=D0=B0=D1=80=D1=82=D0=BE?= =?UTF-8?q?=D1=87=D0=BA=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B3=D1=80=D0=B5=D1=81?= =?UTF-8?q?=D1=81=D0=B0=20=D0=BF=D0=B5=D1=80=D0=B5=D1=81=D1=82=D0=B0=D0=BB?= =?UTF-8?q?=D0=B0=20=D0=B2=D1=8B=D0=B4=D1=83=D0=BC=D1=8B=D0=B2=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20=D1=81=D1=82=D0=B0=D0=B4=D0=B8=D0=B8=20(#3081,=20?= =?UTF-8?q?=D0=B2=D0=B0=D1=80=D0=B8=D0=B0=D0=BD=D1=82=20A)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Запрос оценки один и блокирующий (POST /estimate, mutation.isPending) — промежуточных событий по источникам не существует, а карточка рисовала пофайловые «сбор...» с полосками 60%, счётчик 0/5 и подпись про Celery group с таймаутом. Пользователь не мог отличить «думает» от «завис», а серверная механика текла в UI-текст. Вариант A из задачи (только фронт): - на pending строки нейтральны («ожидает ответ»), раскраска — только по факту estimate.sources_used после ответа; - общая полоса на pending — честная неопределённая анимация вместо выдуманного процента (Math.min(95, ...) убран); - счётчик N/M на pending заменён на «опрашиваем…» (0/5 при идущей работе читался как отказ); - подпись без Celery/таймаута, тон legacy-заглушки «Считаем оценку…»; - недостижимые ветки error/loading у строк удалены (их ничто не выставляло). Вариант B (реальный статус с бэкенда) отклонён сознательно: /estimate остаётся синхронным по решениям #3082/#3083, городить async+task_id ради прогресс-бара — против них. Preview-страница /ui-preview/estimate теперь рендерит ОБА состояния карточки. Проверено скриншотом на dev (pending + done). Co-Authored-By: Claude Opus 5 --- .../src/app/ui-preview/estimate/page.tsx | 3 +- .../components/trade-in/SourcesProgress.tsx | 57 ++++++++----------- .../src/components/trade-in/trade-in.css | 9 +++ 3 files changed, 35 insertions(+), 34 deletions(-) diff --git a/tradein-mvp/frontend/src/app/ui-preview/estimate/page.tsx b/tradein-mvp/frontend/src/app/ui-preview/estimate/page.tsx index 106b02b1..d468b3c1 100644 --- a/tradein-mvp/frontend/src/app/ui-preview/estimate/page.tsx +++ b/tradein-mvp/frontend/src/app/ui-preview/estimate/page.tsx @@ -152,7 +152,8 @@ function PreviewContent() { Оценка стоимости квартиры
- {/* 1. SourcesProgress */} + {/* 1. SourcesProgress — оба состояния: pending (#3081) и done */} + {/* 2. WhatIfPanel */} diff --git a/tradein-mvp/frontend/src/components/trade-in/SourcesProgress.tsx b/tradein-mvp/frontend/src/components/trade-in/SourcesProgress.tsx index 19259ff1..49687642 100644 --- a/tradein-mvp/frontend/src/components/trade-in/SourcesProgress.tsx +++ b/tradein-mvp/frontend/src/components/trade-in/SourcesProgress.tsx @@ -20,7 +20,7 @@ interface SourceRow { key: string; label: string; dotClass: string; - status: "done" | "loading" | "error" | "idle"; + status: "done" | "idle"; count?: number; } @@ -40,11 +40,15 @@ export function SourcesProgress({ estimate, isPending }: Props) { } // Листинговые источники — из реестра. + // #3081: пофайловых статусов во время запроса НЕТ — запрос один и блокирующий + // (POST /estimate, mutation.isPending), промежуточных событий по источникам не + // существует. Строки нейтральны до ответа; раскраска — только по факту + // estimate.sources_used. const listingRows: SourceRow[] = LIVE_LISTING_SOURCES.map((s) => ({ key: s.id, label: s.label, dotClass: s.dot, - status: used.has(s.id) ? "done" : isPending ? "loading" : "idle", + status: used.has(s.id) ? "done" : "idle", count: countBySource[s.id], })); @@ -54,19 +58,16 @@ export function SourcesProgress({ estimate, isPending }: Props) { key: "rosreestr", label: "Росреестр (внутр.)", dotClass: "rosreestr", - status: - isDone && (estimate?.actual_deals.length ?? 0) > 0 - ? "done" - : isPending - ? "loading" - : "idle", + status: isDone && (estimate?.actual_deals.length ?? 0) > 0 ? "done" : "idle", count: estimate?.actual_deals.length, }, ]; const doneCount = rows.filter((r) => r.status === "done").length; const totalCount = rows.length; - const overallPct = isDone ? 100 : Math.min(95, (doneCount / totalCount) * 100 + (isPending ? 10 : 0)); + // #3081: процент не выдумывается — реального прогресса у блокирующего запроса + // нет. На pending полоса неопределённая (CSS-анимация), после ответа — 100. + const overallPct = isDone ? 100 : 0; return (
@@ -77,8 +78,14 @@ export function SourcesProgress({ estimate, isPending }: Props) {
- {doneCount} - / {totalCount} источников + {isPending ? ( + опрашиваем… + ) : ( + <> + {doneCount} + / {totalCount} источников + + )}
@@ -87,13 +94,13 @@ export function SourcesProgress({ estimate, isPending }: Props) {
{isPending - ? "Идёт параллельный запрос — Celery group, timeout 30 сек." + ? "Считаем оценку — обычно занимает несколько секунд…" : isDone ? "Готово. Частичные результаты доступны при недоступных источниках." : "Введите параметры квартиры и нажмите «Оценить»."} -
-
+
+
@@ -106,17 +113,6 @@ export function SourcesProgress({ estimate, isPending }: Props) { )} - {r.status === "loading" && ( - - - - )} - {r.status === "error" && ( - - - - - )} {r.status === "idle" && ( @@ -128,25 +124,20 @@ export function SourcesProgress({ estimate, isPending }: Props) { - + {r.status === "done" && r.count !== undefined && ( <> {r.count} лотов )} {r.status === "done" && r.count === undefined && готово} - {r.status === "loading" && "сбор..."} - {r.status === "error" && "timeout — нет ответа"} {r.status === "idle" && ( - {isDone ? "нет данных" : "ожидает запрос"} + {isDone ? "нет данных" : isPending ? "ожидает ответ" : "ожидает запрос"} )} 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 a7c6e9e0..bd40f5d4 100644 --- a/tradein-mvp/frontend/src/components/trade-in/trade-in.css +++ b/tradein-mvp/frontend/src/components/trade-in/trade-in.css @@ -497,6 +497,15 @@ background: linear-gradient(90deg, var(--accent) 0%, var(--viz-3) 100%); transition: width .3s ease; } + /* #3081: во время запроса реального процента нет — полоса неопределённая */ + .progress-overall.is-indeterminate .bar { + width: 32%; + animation: indeterminate-slide 1.4s ease-in-out infinite; + } + @keyframes indeterminate-slide { + 0% { transform: translateX(-110%); } + 100% { transform: translateX(340%); } + } .progress-eta { font-family: var(--font-mono); font-size: 11px;