Compare commits

..

No commits in common. "2116c3774e2b9cd11f115e21301108e4d05f3b63" and "01ccf4f133fb81a627f963b3abe1175db8c41a7a" have entirely different histories.

3 changed files with 34 additions and 35 deletions

View file

@ -152,8 +152,7 @@ function PreviewContent() {
Оценка стоимости квартиры
</h1>
<section className="result-col">
{/* 1. SourcesProgress — оба состояния: pending (#3081) и done */}
<SourcesProgress estimate={null} isPending={true} />
{/* 1. SourcesProgress */}
<SourcesProgress estimate={FIXTURE_ESTIMATE} isPending={false} />
{/* 2. WhatIfPanel */}

View file

@ -20,7 +20,7 @@ interface SourceRow {
key: string;
label: string;
dotClass: string;
status: "done" | "idle";
status: "done" | "loading" | "error" | "idle";
count?: number;
}
@ -40,15 +40,11 @@ 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" : "idle",
status: used.has(s.id) ? "done" : isPending ? "loading" : "idle",
count: countBySource[s.id],
}));
@ -58,16 +54,19 @@ export function SourcesProgress({ estimate, isPending }: Props) {
key: "rosreestr",
label: "Росреестр (внутр.)",
dotClass: "rosreestr",
status: isDone && (estimate?.actual_deals.length ?? 0) > 0 ? "done" : "idle",
status:
isDone && (estimate?.actual_deals.length ?? 0) > 0
? "done"
: isPending
? "loading"
: "idle",
count: estimate?.actual_deals.length,
},
];
const doneCount = rows.filter((r) => r.status === "done").length;
const totalCount = rows.length;
// #3081: процент не выдумывается — реального прогресса у блокирующего запроса
// нет. На pending полоса неопределённая (CSS-анимация), после ответа — 100.
const overallPct = isDone ? 100 : 0;
const overallPct = isDone ? 100 : Math.min(95, (doneCount / totalCount) * 100 + (isPending ? 10 : 0));
return (
<article className="card progress-card">
@ -78,14 +77,8 @@ export function SourcesProgress({ estimate, isPending }: Props) {
</div>
<div className="card-meta">
<div className="num-done">
{isPending ? (
<span className="total">опрашиваем</span>
) : (
<>
<span data-tnum>{doneCount}</span>
<span className="total"> / {totalCount}</span> источников
</>
)}
<span data-tnum>{doneCount}</span>
<span className="total"> / {totalCount}</span> источников
</div>
</div>
</div>
@ -94,13 +87,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${isPending ? " is-indeterminate" : ""}`}>
<div className="bar" style={isPending ? undefined : { width: `${overallPct}%` }} />
<div className="progress-overall">
<div className="bar" style={{ width: `${overallPct}%` }} />
</div>
</div>
@ -113,6 +106,17 @@ 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" />
@ -124,20 +128,25 @@ export function SourcesProgress({ estimate, isPending }: Props) {
<i
style={{
// @ts-expect-error CSS custom prop
"--p": r.status === "done" ? "100%" : "0%",
"--p": r.status === "done" ? "100%" : r.status === "loading" ? "60%" : "0%",
}}
/>
</span>
<span className="src-value">
<span
className="src-value"
style={{ color: r.status === "error" ? "var(--danger)" : undefined }}
>
{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 ? "нет данных" : isPending ? "ожидает ответ" : "ожидает запрос"}
{isDone ? "нет данных" : "ожидает запрос"}
</span>
)}
</span>

View file

@ -497,15 +497,6 @@
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;