gendesign/tradein-mvp/frontend/src/components/trade-in/v2/ObjectSummary.tsx
bot-backend 30e158ad2a
All checks were successful
CI / changes (pull_request) Successful in 8s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
fix(tradein/v2): UI/UX-аудит P1 quick-wins (#2062)
Честная полировка живого /trade-in/v2 без правок архитектуры (FE-only).

- Провенанс из data, без хардкодов: footer «ПОСТРОЕНО ПО N АНАЛОГАМ И M
  СДЕЛКАМ» из meta.builtOn; единый счётчик источников (activeSourceCount =
  залитые плитки) в шапке 02, СВОДКЕ и оверлее; убраны «6/10», «2/7»,
  «Avito·10», «из 2 источников».
- Единый ru-форматтер: запятая везде (CV/площадь/%), NBSP число+единица,
  один en-dash и одна точность для денежных диапазонов.
- CTA «ОЦЕНИТЬ КВАРТИРУ» → solid brand-blue + белый текст; :focus-visible
  HUD-glow на инпутах и кнопке.
- Лендмарки <nav>/<main>/<footer> (display:contents, 0 визуальных изменений);
  scale-fit артборда — на 1366×768 / 1440×900 ужимается, не режется.
- Мини-карта: честный зум (min=1, без пустых краёв при отдалении), убран
  мёртвый crosshair-двойник, расклеены налезающие метки цен.
- CRM-дропдаун явно задизейблен («скоро», без ▼); убран «ШАГ 1/1».
- Карточка 02 → «ОЖИДАЕМАЯ ЦЕНА СДЕЛКИ · с учётом торга» + строка-примирение
  с медианой ДКП; КОЭФ.ЛОКАЦИИ → muted-пилюля «скоро».
- Бейдж СВОДКИ «В РАСЧЁТЕ» → «АКТУАЛЬНО»; скрыты целиком-прочерк колонки
  (ИСТОЧНИКОВ в 07, ОБЪЯВЛЕНИЕ ДО СДЕЛКИ в 04) data-driven.
2026-06-28 19:33:12 +03:00

312 lines
8.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 03 СВОДКА ОБЪЕКТА — right-rail summary card for the /trade-in/v2 "МЕРА Оценка"
// design port. Faithful markup port of МЕРА Оценка.dc.html lines 397-422:
// address + "В РАСЧЁТЕ" badge, four clickable per-section totals, and a
// "КАЧЕСТВО ДАННЫХ" block with a donut. Data from fixtures; clicks fire
// onNavigate(row.nav) only — no API, no fetching.
"use client";
import { tokens } from "./tokens";
import { object, summary } from "./fixtures";
import type { ObjectSummaryData } from "./mappers";
// Default presentation data (unwired usage): the existing design fixtures.
const OBJECT_SUMMARY_FIXTURE: ObjectSummaryData = { object, summary };
interface ObjectSummaryProps {
data?: ObjectSummaryData;
onNavigate: (i: number) => void;
}
export function ObjectSummary({
data = OBJECT_SUMMARY_FIXTURE,
onNavigate,
}: ObjectSummaryProps) {
return (
<div
style={{
position: "relative",
background: tokens.surface.w50,
backdropFilter: "blur(6px)",
border: `1px solid ${tokens.line2}`,
borderRadius: 8,
padding: "15px 16px",
flex: 1,
display: "flex",
flexDirection: "column",
color: tokens.ink,
fontFamily: tokens.font.sans,
}}
>
<style>{`
.os-row { transition: background .12s; }
.os-row:hover { background: rgba(46,139,255,.07); }
`}</style>
{/* HUD corner bracket */}
<div
style={{
position: "absolute",
right: -1,
top: -1,
width: 12,
height: 12,
borderRight: `1.5px solid ${tokens.bracket}`,
borderTop: `1.5px solid ${tokens.bracket}`,
}}
/>
{/* Header: 03 СВОДКА ОБЪЕКТА + В РАСЧЁТЕ badge */}
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
marginBottom: 5,
}}
>
<div style={{ display: "flex", alignItems: "baseline", gap: 9 }}>
<span
style={{
fontFamily: tokens.font.mono,
fontSize: 14,
color: tokens.accent,
}}
>
03
</span>
<span style={{ fontSize: 12, fontWeight: 600, letterSpacing: 1.5 }}>
СВОДКА ОБЪЕКТА
</span>
</div>
<span
style={{
display: "flex",
alignItems: "center",
gap: 4,
fontSize: 8,
letterSpacing: 0.8,
color: tokens.success,
border: `1px solid ${tokens.successBorder}`,
borderRadius: 20,
padding: "2px 7px",
whiteSpace: "nowrap",
}}
>
<span
style={{
width: 5,
height: 5,
borderRadius: "50%",
background: tokens.success,
}}
/>
АКТУАЛЬНО
</span>
</div>
<div
style={{
fontSize: 9,
letterSpacing: 1.2,
color: tokens.muted3,
marginBottom: 13,
}}
>
ИТОГИ ПО РАЗДЕЛАМ
</div>
{/* Address */}
<div
style={{
display: "flex",
alignItems: "flex-start",
gap: 9,
marginBottom: 14,
}}
>
<svg
width="15"
height="18"
viewBox="0 0 15 18"
fill="none"
style={{ marginTop: 1 }}
>
<path
d="M7.5 17C7.5 17 14 11 14 6.5A6.5 6.5 0 1 0 1 6.5C1 11 7.5 17 7.5 17Z"
stroke={tokens.accent}
strokeWidth="1.2"
/>
<circle cx="7.5" cy="6.5" r="2.3" fill={tokens.accent} />
</svg>
<div style={{ flex: 1 }}>
<div style={{ fontSize: 13, fontWeight: 600 }}>
{data.object.address}
</div>
<div style={{ fontSize: 11, color: tokens.muted, marginTop: 2 }}>
{data.object.city}
</div>
</div>
<svg width="13" height="13" viewBox="0 0 13 13" fill="none">
<path
d="M5 1h7v7M12 1L6 7M9 8v4H1V4h4"
stroke={tokens.muted2}
strokeWidth="1"
/>
</svg>
</div>
{/* Per-section totals (clickable) */}
<div style={{ display: "flex", flexDirection: "column", gap: 0 }}>
{data.summary.rows.map((row, i) => (
<div
key={row.label}
className="os-row"
onClick={() => onNavigate(row.nav)}
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "9px 6px",
borderBottom:
i < data.summary.rows.length - 1
? `1px dotted ${tokens.lineGradient}`
: undefined,
cursor: "pointer",
borderRadius: 5,
}}
>
<span
style={{
fontSize: 10.5,
color: tokens.body,
display: "flex",
alignItems: "center",
gap: 6,
whiteSpace: "nowrap",
paddingRight: 12,
}}
>
<span
style={{
width: 5,
height: 5,
borderRadius: "50%",
background:
row.dot === "accent" ? tokens.accent : tokens.scatterDeal,
}}
/>
{row.label}
</span>
<span style={{ display: "flex", alignItems: "center", gap: 8 }}>
<span
style={{
fontFamily: tokens.font.mono,
fontSize: 11,
color: tokens.ink2,
whiteSpace: "nowrap",
}}
>
{row.value}
</span>
<span style={{ color: tokens.accent, fontSize: 11 }}></span>
</span>
</div>
))}
</div>
{/* КАЧЕСТВО ДАННЫХ */}
<div
style={{
marginTop: 14,
background: tokens.infoSoftBg,
border: `1px solid ${tokens.infoSoftBorder}`,
borderRadius: 6,
padding: 12,
display: "flex",
gap: 12,
alignItems: "center",
}}
>
<svg width="50" height="50" viewBox="0 0 50 50" fill="none">
<circle
cx="25"
cy="25"
r="20"
stroke={tokens.track}
strokeWidth="3"
/>
<circle
cx="25"
cy="25"
r="20"
stroke={tokens.accent}
strokeWidth="3"
strokeLinecap="round"
strokeDasharray="126"
strokeDashoffset="72"
transform="rotate(-90 25 25)"
/>
<circle cx="25" cy="25" r="11" stroke="#cdddec" strokeWidth="1" />
<circle cx="25" cy="25" r="1.6" fill={tokens.accent} />
</svg>
<div style={{ flex: 1 }}>
<div
style={{
fontSize: 9.5,
letterSpacing: 1.5,
color: tokens.muted2,
marginBottom: 7,
}}
>
КАЧЕСТВО ДАННЫХ
</div>
<div
style={{
display: "flex",
justifyContent: "space-between",
fontSize: 10,
color: tokens.muted,
lineHeight: 1.7,
}}
>
<span>Источников</span>
<span style={{ fontFamily: tokens.font.mono, color: tokens.ink }}>
{data.summary.quality.sources}
</span>
</div>
<div
style={{
display: "flex",
justifyContent: "space-between",
fontSize: 10,
color: tokens.muted,
lineHeight: 1.7,
}}
>
<span>Достоверность</span>
<span style={{ color: tokens.ink }}>
{data.summary.quality.confidence}
</span>
</div>
<div
style={{
display: "flex",
justifyContent: "space-between",
fontSize: 10,
color: tokens.muted,
lineHeight: 1.7,
}}
>
<span>CV</span>
<span
style={{ fontFamily: tokens.font.mono, color: tokens.accent }}
>
{data.summary.quality.cv}
</span>
</div>
</div>
</div>
</div>
);
}