gendesign/tradein-mvp/frontend/src/components/trade-in/v2/LocationDrawer.tsx
bot-backend 5a2b6d91cd
All checks were successful
CI / changes (pull_request) Successful in 7s
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): audit findings — de-fabricate location drawer, real map markers, validation, hero image, real user (#2036)
Browser audit of the live /trade-in/v2 found honesty + UX defects; fixes:
- 🔴 LocationDrawer: removed FABRICATED coef 0.87 / fake 'base×coef=result' / invented
  POI factors / false 'OpenStreetMap' source. Now honest static 'ПОЯСНЕНИЕ К РАСЧЁТУ'
  (real methodology) + 'коэф. локации в разработке' note. + Escape-to-close.
- 🔴 Map (ParamsPanel): removed static fixture price markers; renders REAL analog
  markers via new mapMarkers(estimate) (projects lat/lon around target, cap 6).
- ParamsPanel: empty площадь/address now shows a validation message instead of
  silently blocking submit; numeric placeholders made clearly placeholders ('напр. 54').
- HeroBar: dead 'КАК РАССЧИТАНО' button now opens the info drawer; building.png next/image
  400 fixed (unoptimized + onError fallback — basePath wasn't applied to the optimizer URL).
- TopNav: real user from useMe() (name/initials/org/email) instead of fixture
  'Андрей Петров'; dead Профиль/Настройки/Помощь dimmed/disabled; Выйти → logout().
- mappers: de-glue analog addresses ('69/2Геологическая'→'69/2 Геологическая', preserves
  house letters like 14А); 'Продажи в доме'→'Продажи рядом' (radius data); торг sign fixed
  (backend (start−last)/start positive=reduction → renders '−3.3%').
- HistoryView: '0 ЛОТА' → correct plural via pluralRu.

next build green; code-reviewer  APPROVE.
2026-06-28 17:26:12 +03:00

236 lines
7.1 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.

"use client";
// "ПОЯСНЕНИЕ К РАСЧЁТУ" right-side drawer for the /trade-in/v2 "МЕРА Оценка"
// design port. Opened from HeroBar (the "?" near "КОЭФ. ЛОКАЦИИ"). This used to
// render a FABRICATED location coefficient (0.87), a fake "base × coef = result"
// formula and invented POI factor lists with a false "Источник: OpenStreetMap"
// footer — none of which the backend produces (location-coef is deferred,
// backend #2045). It is now an HONEST methodology panel with STATIC, truthful
// documentation text only: how the estimate is built, and an explicit note that
// the location coefficient does not exist yet. Keeps the same drawer shell /
// slide animation / close button. Open/close is driven entirely by props; the
// only effect is an Escape-to-close keydown listener while open.
import { useEffect } from "react";
import { tokens } from "./tokens";
interface LocationDrawerProps {
open: boolean;
onClose: () => void;
}
export function LocationDrawer({ open, onClose }: LocationDrawerProps) {
// Escape-to-close (UI effect only — no HTTP). Active while the drawer is open.
useEffect(() => {
if (!open) return;
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [open, onClose]);
return (
<>
<style>{`
.ld-close {
width: 32px;
height: 32px;
flex: 0 0 auto;
border: 1px solid ${tokens.line};
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: ${tokens.muted};
font-size: 16px;
background: ${tokens.surface.w60};
transition: all .15s;
}
.ld-close:hover {
border-color: ${tokens.accent};
color: ${tokens.accent};
}
`}</style>
{/* scrim */}
<div
onClick={onClose}
style={{
position: "absolute",
inset: 0,
background: "rgba(22,42,70,.32)",
backdropFilter: "blur(2px)",
opacity: open ? 1 : 0,
pointerEvents: open ? "auto" : "none",
transition: "opacity .3s",
zIndex: 40,
}}
/>
{/* drawer */}
<div
style={{
position: "absolute",
top: 0,
right: 0,
bottom: 0,
width: 452,
transform: open ? "translateX(0)" : "translateX(108%)",
transition: "transform .38s cubic-bezier(.22,.61,.36,1)",
zIndex: 41,
background:
"linear-gradient(180deg,rgba(245,249,253,.97),rgba(235,242,250,.97))",
backdropFilter: "blur(10px)",
borderLeft: `1px solid ${tokens.line3}`,
boxShadow: "-18px 0 50px rgba(40,80,130,.16)",
display: "flex",
flexDirection: "column",
padding: "30px 32px",
overflowY: "auto",
fontFamily: tokens.font.sans,
}}
>
{/* left accent rail */}
<div
style={{
position: "absolute",
left: 0,
top: 18,
bottom: 18,
width: 1,
background: `linear-gradient(180deg,transparent,${tokens.accent},transparent)`,
opacity: 0.5,
}}
/>
{/* corner brackets */}
<div
style={{
position: "absolute",
left: 14,
top: 14,
width: 16,
height: 16,
borderLeft: `1.5px solid ${tokens.accent}`,
borderTop: `1.5px solid ${tokens.accent}`,
}}
/>
<div
style={{
position: "absolute",
right: 14,
bottom: 14,
width: 16,
height: 16,
borderRight: `1.5px solid ${tokens.accent}`,
borderBottom: `1.5px solid ${tokens.accent}`,
}}
/>
{/* header */}
<div
style={{
display: "flex",
alignItems: "flex-start",
justifyContent: "space-between",
}}
>
<div>
<div
style={{
fontSize: 9,
letterSpacing: "3px",
color: tokens.muted2,
}}
>
МЕТОДИКА
</div>
<div
style={{
fontSize: 22,
fontWeight: 300,
letterSpacing: "1.5px",
color: tokens.ink2,
marginTop: 8,
}}
>
ПОЯСНЕНИЕ К РАСЧЁТУ
</div>
</div>
<div className="ld-close" onClick={onClose}>
</div>
</div>
{/* divider under header */}
<div
style={{
marginTop: 22,
borderBottom: `1px solid ${tokens.lineSoft}`,
}}
/>
{/* methodology section */}
<div
style={{
fontSize: 9,
letterSpacing: "2px",
color: tokens.muted2,
marginTop: 22,
marginBottom: 12,
}}
>
КАК СЧИТАЕМ
</div>
<div
style={{
fontSize: 12.5,
lineHeight: 1.7,
color: tokens.body2,
}}
>
Агрегируем объявления (Авито, Циан, Яндекс, Домклик) и сделки
Росреестра по сопоставимым квартирам. Медиана{" "}
<b style={{ color: tokens.ink2 }}>/м²</b> 3 оценки:{" "}
<b style={{ color: tokens.ink2 }}>
рекомендованная цена в объявлении
</b>{" "}
(asking),{" "}
<b style={{ color: tokens.ink2 }}>ожидаемая цена сделки</b> (asking
торг по ДКП),{" "}
<b style={{ color: tokens.ink2 }}>ДКП·Росреестр</b> (фактические
сделки). CV коэффициент вариации выборки (разброс цен).
</div>
{/* location note section */}
<div
style={{
fontSize: 9,
letterSpacing: "2px",
color: tokens.muted2,
marginTop: 26,
marginBottom: 12,
}}
>
ЛОКАЦИЯ
</div>
<div
style={{
fontSize: 12.5,
lineHeight: 1.7,
color: tokens.body2,
background: tokens.infoSoftBg,
border: `1px solid ${tokens.infoSoftBorder}`,
borderRadius: 7,
padding: "12px 14px",
}}
>
Коэффициент локации (POI, транспорт, шум) {" "}
<b style={{ color: tokens.ink2 }}>в разработке</b>; текущая оценка по
локации не корректируется.
</div>
</div>
</>
);
}