Merge pull request 'fix(mera/ui): карточка прогресса перестала выдумывать стадии (#3081, вариант A)' (#3101) from fix/3081-honest-progress into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / deploy (push) Successful in 1m18s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / build-frontend (push) Successful in 2m29s
Deploy Trade-In / test (push) Successful in 3m51s
Deploy Trade-In / build-backend (push) Successful in 35s
Deploy Trade-In / perimeter-smoke (push) Successful in 10s
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / deploy (push) Successful in 1m18s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / build-frontend (push) Successful in 2m29s
Deploy Trade-In / test (push) Successful in 3m51s
Deploy Trade-In / build-backend (push) Successful in 35s
Deploy Trade-In / perimeter-smoke (push) Successful in 10s
This commit is contained in:
commit
2116c3774e
3 changed files with 35 additions and 34 deletions
|
|
@ -152,7 +152,8 @@ function PreviewContent() {
|
||||||
Оценка стоимости квартиры
|
Оценка стоимости квартиры
|
||||||
</h1>
|
</h1>
|
||||||
<section className="result-col">
|
<section className="result-col">
|
||||||
{/* 1. SourcesProgress */}
|
{/* 1. SourcesProgress — оба состояния: pending (#3081) и done */}
|
||||||
|
<SourcesProgress estimate={null} isPending={true} />
|
||||||
<SourcesProgress estimate={FIXTURE_ESTIMATE} isPending={false} />
|
<SourcesProgress estimate={FIXTURE_ESTIMATE} isPending={false} />
|
||||||
|
|
||||||
{/* 2. WhatIfPanel */}
|
{/* 2. WhatIfPanel */}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ interface SourceRow {
|
||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
dotClass: string;
|
dotClass: string;
|
||||||
status: "done" | "loading" | "error" | "idle";
|
status: "done" | "idle";
|
||||||
count?: number;
|
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) => ({
|
const listingRows: SourceRow[] = LIVE_LISTING_SOURCES.map((s) => ({
|
||||||
key: s.id,
|
key: s.id,
|
||||||
label: s.label,
|
label: s.label,
|
||||||
dotClass: s.dot,
|
dotClass: s.dot,
|
||||||
status: used.has(s.id) ? "done" : isPending ? "loading" : "idle",
|
status: used.has(s.id) ? "done" : "idle",
|
||||||
count: countBySource[s.id],
|
count: countBySource[s.id],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -54,19 +58,16 @@ export function SourcesProgress({ estimate, isPending }: Props) {
|
||||||
key: "rosreestr",
|
key: "rosreestr",
|
||||||
label: "Росреестр (внутр.)",
|
label: "Росреестр (внутр.)",
|
||||||
dotClass: "rosreestr",
|
dotClass: "rosreestr",
|
||||||
status:
|
status: isDone && (estimate?.actual_deals.length ?? 0) > 0 ? "done" : "idle",
|
||||||
isDone && (estimate?.actual_deals.length ?? 0) > 0
|
|
||||||
? "done"
|
|
||||||
: isPending
|
|
||||||
? "loading"
|
|
||||||
: "idle",
|
|
||||||
count: estimate?.actual_deals.length,
|
count: estimate?.actual_deals.length,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const doneCount = rows.filter((r) => r.status === "done").length;
|
const doneCount = rows.filter((r) => r.status === "done").length;
|
||||||
const totalCount = rows.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 (
|
return (
|
||||||
<article className="card progress-card">
|
<article className="card progress-card">
|
||||||
|
|
@ -77,8 +78,14 @@ export function SourcesProgress({ estimate, isPending }: Props) {
|
||||||
</div>
|
</div>
|
||||||
<div className="card-meta">
|
<div className="card-meta">
|
||||||
<div className="num-done">
|
<div className="num-done">
|
||||||
<span data-tnum>{doneCount}</span>
|
{isPending ? (
|
||||||
<span className="total"> / {totalCount}</span> источников
|
<span className="total">опрашиваем…</span>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span data-tnum>{doneCount}</span>
|
||||||
|
<span className="total"> / {totalCount}</span> источников
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -87,13 +94,13 @@ export function SourcesProgress({ estimate, isPending }: Props) {
|
||||||
<div className="progress-summary">
|
<div className="progress-summary">
|
||||||
<span className="lead">
|
<span className="lead">
|
||||||
{isPending
|
{isPending
|
||||||
? "Идёт параллельный запрос — Celery group, timeout 30 сек."
|
? "Считаем оценку — обычно занимает несколько секунд…"
|
||||||
: isDone
|
: isDone
|
||||||
? "Готово. Частичные результаты доступны при недоступных источниках."
|
? "Готово. Частичные результаты доступны при недоступных источниках."
|
||||||
: "Введите параметры квартиры и нажмите «Оценить»."}
|
: "Введите параметры квартиры и нажмите «Оценить»."}
|
||||||
</span>
|
</span>
|
||||||
<div className="progress-overall">
|
<div className={`progress-overall${isPending ? " is-indeterminate" : ""}`}>
|
||||||
<div className="bar" style={{ width: `${overallPct}%` }} />
|
<div className="bar" style={isPending ? undefined : { width: `${overallPct}%` }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -106,17 +113,6 @@ export function SourcesProgress({ estimate, isPending }: Props) {
|
||||||
<polyline points="20 6 9 17 4 12" />
|
<polyline points="20 6 9 17 4 12" />
|
||||||
</svg>
|
</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" && (
|
{r.status === "idle" && (
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
<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" />
|
<circle cx="12" cy="12" r="9" />
|
||||||
|
|
@ -128,25 +124,20 @@ export function SourcesProgress({ estimate, isPending }: Props) {
|
||||||
<i
|
<i
|
||||||
style={{
|
style={{
|
||||||
// @ts-expect-error CSS custom prop
|
// @ts-expect-error CSS custom prop
|
||||||
"--p": r.status === "done" ? "100%" : r.status === "loading" ? "60%" : "0%",
|
"--p": r.status === "done" ? "100%" : "0%",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span className="src-value">
|
||||||
className="src-value"
|
|
||||||
style={{ color: r.status === "error" ? "var(--danger)" : undefined }}
|
|
||||||
>
|
|
||||||
{r.status === "done" && r.count !== undefined && (
|
{r.status === "done" && r.count !== undefined && (
|
||||||
<>
|
<>
|
||||||
<b>{r.count} лотов</b>
|
<b>{r.count} лотов</b>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{r.status === "done" && r.count === undefined && <b>готово</b>}
|
{r.status === "done" && r.count === undefined && <b>готово</b>}
|
||||||
{r.status === "loading" && "сбор..."}
|
|
||||||
{r.status === "error" && "timeout — нет ответа"}
|
|
||||||
{r.status === "idle" && (
|
{r.status === "idle" && (
|
||||||
<span style={{ color: "var(--muted-2)" }}>
|
<span style={{ color: "var(--muted-2)" }}>
|
||||||
{isDone ? "нет данных" : "ожидает запрос"}
|
{isDone ? "нет данных" : isPending ? "ожидает ответ" : "ожидает запрос"}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -497,6 +497,15 @@
|
||||||
background: linear-gradient(90deg, var(--accent) 0%, var(--viz-3) 100%);
|
background: linear-gradient(90deg, var(--accent) 0%, var(--viz-3) 100%);
|
||||||
transition: width .3s ease;
|
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 {
|
.progress-eta {
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue