fix(mera/ui): карточка прогресса перестала выдумывать стадии (#3081, вариант A)
All checks were successful
CI / changes (pull_request) Successful in 11s
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 59s
All checks were successful
CI / changes (pull_request) Successful in 11s
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 59s
Запрос оценки один и блокирующий (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 <noreply@anthropic.com>
This commit is contained in:
parent
729e9acc52
commit
a4604dd9db
3 changed files with 35 additions and 34 deletions
|
|
@ -152,7 +152,8 @@ function PreviewContent() {
|
|||
Оценка стоимости квартиры
|
||||
</h1>
|
||||
<section className="result-col">
|
||||
{/* 1. SourcesProgress */}
|
||||
{/* 1. SourcesProgress — оба состояния: pending (#3081) и done */}
|
||||
<SourcesProgress estimate={null} isPending={true} />
|
||||
<SourcesProgress estimate={FIXTURE_ESTIMATE} isPending={false} />
|
||||
|
||||
{/* 2. WhatIfPanel */}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<article className="card progress-card">
|
||||
|
|
@ -77,8 +78,14 @@ export function SourcesProgress({ estimate, isPending }: Props) {
|
|||
</div>
|
||||
<div className="card-meta">
|
||||
<div className="num-done">
|
||||
<span data-tnum>{doneCount}</span>
|
||||
<span className="total"> / {totalCount}</span> источников
|
||||
{isPending ? (
|
||||
<span className="total">опрашиваем…</span>
|
||||
) : (
|
||||
<>
|
||||
<span data-tnum>{doneCount}</span>
|
||||
<span className="total"> / {totalCount}</span> источников
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -87,13 +94,13 @@ export function SourcesProgress({ estimate, isPending }: Props) {
|
|||
<div className="progress-summary">
|
||||
<span className="lead">
|
||||
{isPending
|
||||
? "Идёт параллельный запрос — Celery group, timeout 30 сек."
|
||||
? "Считаем оценку — обычно занимает несколько секунд…"
|
||||
: isDone
|
||||
? "Готово. Частичные результаты доступны при недоступных источниках."
|
||||
: "Введите параметры квартиры и нажмите «Оценить»."}
|
||||
</span>
|
||||
<div className="progress-overall">
|
||||
<div className="bar" style={{ width: `${overallPct}%` }} />
|
||||
<div className={`progress-overall${isPending ? " is-indeterminate" : ""}`}>
|
||||
<div className="bar" style={isPending ? undefined : { width: `${overallPct}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -106,17 +113,6 @@ export function SourcesProgress({ estimate, isPending }: Props) {
|
|||
<polyline points="20 6 9 17 4 12" />
|
||||
</svg>
|
||||
)}
|
||||
{r.status === "loading" && (
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
||||
</svg>
|
||||
)}
|
||||
{r.status === "error" && (
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18" />
|
||||
<line x1="6" y1="6" x2="18" y2="18" />
|
||||
</svg>
|
||||
)}
|
||||
{r.status === "idle" && (
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
|
|
@ -128,25 +124,20 @@ export function SourcesProgress({ estimate, isPending }: Props) {
|
|||
<i
|
||||
style={{
|
||||
// @ts-expect-error CSS custom prop
|
||||
"--p": r.status === "done" ? "100%" : r.status === "loading" ? "60%" : "0%",
|
||||
"--p": r.status === "done" ? "100%" : "0%",
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
className="src-value"
|
||||
style={{ color: r.status === "error" ? "var(--danger)" : undefined }}
|
||||
>
|
||||
<span className="src-value">
|
||||
{r.status === "done" && r.count !== undefined && (
|
||||
<>
|
||||
<b>{r.count} лотов</b>
|
||||
</>
|
||||
)}
|
||||
{r.status === "done" && r.count === undefined && <b>готово</b>}
|
||||
{r.status === "loading" && "сбор..."}
|
||||
{r.status === "error" && "timeout — нет ответа"}
|
||||
{r.status === "idle" && (
|
||||
<span style={{ color: "var(--muted-2)" }}>
|
||||
{isDone ? "нет данных" : "ожидает запрос"}
|
||||
{isDone ? "нет данных" : isPending ? "ожидает ответ" : "ожидает запрос"}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue