gendesign/tradein-mvp/frontend/src/components/trade-in/v2/SourcesView.tsx
bot-backend fcb85deab8
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 1m3s
CI / openapi-codegen-check (pull_request) Successful in 1m59s
refactor(tradein/v2): перенос HUD v2 в tradein-mvp frontend
Прод-/trade-in/* обслуживает tradein-mvp/frontend (basePath=/trade-in,
standalone), а не главный frontend — v2 по ошибке попал в главный, где
прод его не отдаёт. Markup-only порт (fixtures, без API):

- компоненты v2 → tradein-mvp/frontend/src/components/trade-in/v2/
- роут src/app/v2/ (basePath даёт прод-URL /trade-in/v2)
- public/trade-in-v2/building.png
- HeroBar: <img> → next/image (плоский img не префиксит basePath → 404)
- удалён мёртвый v2 из главного frontend

next build (NEXT_PUBLIC_BASE_PATH=/trade-in) зелёный, роут /v2 prerendered.
2026-06-28 13:10:23 +03:00

401 lines
10 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.

import type { CSSProperties } from "react";
import { tokens } from "./tokens";
import { adRows, dealRows, marketAds, marketDeals } from "./fixtures";
import type { AdRow, DealRow } from "./types";
// Overlay 05 — РЫНОК · АНАЛОГИ И СДЕЛКИ.
// Faithful markup port from "МЕРА Оценка.dc.html" (lines 492-537).
// Card A: объявления в продаже (3 KPI + filter chips + adRows table).
// Card B: фактические сделки (3 KPI + dealRows table).
// Static markup: data from fixtures, no fetching, no interactive state.
// One-off tints not present in the v2 palette (kept literal, faithful to design).
const KPI_BG = "rgba(238,244,250,.5)";
const BADGE_BG = "#eaf1f8";
const AVITO_DOT = "#e8743b";
const AD_GRID = "2.4fr 1fr 1fr 1.2fr .7fr 1fr";
const DEAL_GRID = "2.6fr 1.2fr 1fr 1.2fr 1fr";
const card: CSSProperties = {
background: tokens.surface.w55,
border: `1px solid ${tokens.line2}`,
borderRadius: 8,
overflow: "hidden",
};
const cardHeader: CSSProperties = {
display: "flex",
alignItems: "flex-start",
justifyContent: "space-between",
padding: "14px 18px",
borderBottom: `1px solid ${tokens.lineSoft}`,
};
const eyebrow: CSSProperties = {
fontSize: "8.5px",
letterSpacing: "2px",
color: tokens.muted2,
marginBottom: 5,
};
const cardTitle: CSSProperties = {
fontSize: "12px",
fontWeight: 600,
color: tokens.ink2,
};
const headerRight: CSSProperties = {
textAlign: "right",
fontSize: "9.5px",
color: tokens.muted2,
lineHeight: 1.6,
};
const kpiRow: CSSProperties = {
display: "grid",
gridTemplateColumns: "repeat(3,1fr)",
borderBottom: `1px solid ${tokens.lineSoft}`,
background: KPI_BG,
};
const kpiLabel: CSSProperties = {
fontSize: "9px",
letterSpacing: "1.5px",
color: tokens.muted2,
};
const tableHead: CSSProperties = {
gap: "10px",
padding: "9px 18px",
fontSize: "9px",
letterSpacing: "1px",
color: tokens.muted2,
borderBottom: `1px solid ${tokens.lineSoft2}`,
};
const tableRow: CSSProperties = {
gap: "10px",
padding: "11px 18px",
fontSize: "11px",
borderBottom: `1px solid ${tokens.lineSoft3}`,
alignItems: "center",
};
const metaSpan: CSSProperties = {
color: tokens.muted2,
fontFamily: tokens.font.mono,
fontSize: "9.5px",
};
const badge: CSSProperties = {
fontSize: "9px",
background: BADGE_BG,
border: `1px solid ${tokens.line2}`,
borderRadius: 3,
padding: "2px 8px",
color: tokens.muted,
};
const chip: CSSProperties = {
fontSize: "10px",
color: tokens.muted,
border: `1px solid ${tokens.line2}`,
borderRadius: 20,
padding: "3px 10px",
};
const monoMuted: CSSProperties = {
fontFamily: tokens.font.mono,
color: tokens.muted,
};
const monoInk: CSSProperties = {
fontFamily: tokens.font.mono,
color: tokens.ink2,
};
const monoMuted2: CSSProperties = {
fontFamily: tokens.font.mono,
color: tokens.muted2,
};
function KpiCell({
label,
value,
unit,
valueColor = tokens.ink2,
unitColor = tokens.muted,
last = false,
}: {
label: string;
value: string;
unit: string;
valueColor?: string;
unitColor?: string;
last?: boolean;
}) {
return (
<div
style={{
padding: "12px 18px",
borderRight: last ? undefined : `1px solid ${tokens.lineSoft2}`,
}}
>
<div style={kpiLabel}>{label}</div>
<div
style={{
fontFamily: tokens.font.mono,
fontSize: "22px",
fontWeight: 300,
color: valueColor,
marginTop: 4,
}}
>
{value}{" "}
<span style={{ fontSize: "11px", color: unitColor }}>{unit}</span>
</div>
</div>
);
}
export default function SourcesView() {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 22 }}>
<style>{`
.sv-row { transition: background .12s ease; }
.sv-row:hover { background: ${tokens.surface.w50}; }
.sv-rr { transition: background .12s ease, border-color .12s ease; }
.sv-rr:hover { background: ${BADGE_BG}; border-color: ${tokens.accent}; }
`}</style>
{/* объявления */}
<div style={card}>
<div style={cardHeader}>
<div>
<div style={eyebrow}>ПОХОЖИЕ КВАРТИРЫ · В ПРОДАЖЕ</div>
<div style={cardTitle}>
Объявления аналогичные квартиры в продаже
</div>
</div>
<div style={headerRight}>
Обновлено: <br />
Источников: <b style={{ color: tokens.muted }}>2 / 7</b>
</div>
</div>
<div style={kpiRow}>
<KpiCell
label="ОБЪЯВЛЕНИЙ ПО АНАЛОГАМ"
value={marketAds.kpi.count}
unit="шт · из 2 источников"
/>
<KpiCell
label="МЕДИАНА"
value={marketAds.kpi.median}
unit={`млн ₽ · ${marketAds.kpi.ppm} ₽/м²`}
/>
<KpiCell
label="ДИАПАЗОН P25P75"
value={marketAds.kpi.range}
unit={`млн · CV ${marketAds.kpi.cv}`}
valueColor={tokens.accent}
last
/>
</div>
<div
style={{
display: "flex",
alignItems: "center",
gap: 10,
padding: "11px 18px",
borderBottom: `1px solid ${tokens.lineSoft3}`,
}}
>
<span
style={{
fontSize: "9px",
letterSpacing: "1.5px",
color: tokens.muted2,
}}
>
ФИЛЬТР
</span>
{marketAds.filters.map((f, i) => (
<span key={i} style={chip}>
{f}
</span>
))}
<span
style={{
marginLeft: "auto",
fontSize: "10px",
color: tokens.muted,
display: "flex",
alignItems: "center",
gap: 6,
}}
>
<span
style={{
width: 8,
height: 8,
borderRadius: 2,
background: AVITO_DOT,
}}
/>
Avito · 10
</span>
</div>
<div
style={{
...tableHead,
display: "grid",
gridTemplateColumns: AD_GRID,
}}
>
<span>АДРЕС</span>
<span>ИСТОЧНИК</span>
<span>/М²</span>
<span>СТОИМОСТЬ</span>
<span>ДИСТ.</span>
<span />
</div>
{adRows.map((r: AdRow, i) => (
<div
key={i}
className="sv-row"
style={{
...tableRow,
display: "grid",
gridTemplateColumns: AD_GRID,
}}
>
<span style={{ color: tokens.body }}>
{r.addr}
<span style={metaSpan}>
{" · "}
{r.meta}
</span>
</span>
<span>
<span style={badge}>Avito</span>
</span>
<span style={monoMuted}>{r.ppm}</span>
<span style={monoInk}>{r.price}</span>
<span style={monoMuted2}></span>
<span
className="sv-rr"
style={{
fontSize: "9.5px",
color: tokens.accent,
border: `1px solid ${tokens.line2}`,
borderRadius: 4,
padding: "3px 8px",
textAlign: "center",
}}
>
Росреестр
</span>
</div>
))}
</div>
{/* фактические сделки */}
<div style={card}>
<div style={cardHeader}>
<div>
<div style={eyebrow}>ПОХОЖИЕ КВАРТИРЫ · СДЕЛКИ</div>
<div style={cardTitle}>
Фактические сделки по аналогичным квартирам
</div>
</div>
<div style={headerRight}>
Период: <b style={{ color: tokens.muted }}>за 12 мес.</b>
<br />
проверенные данные · подтверждено Росреестром
</div>
</div>
<div style={kpiRow}>
<KpiCell
label="СДЕЛОК ПО АНАЛОГАМ"
value={marketDeals.kpi.count}
unit="шт · Росреестр"
/>
<KpiCell
label="МЕДИАНА СДЕЛОК"
value={marketDeals.kpi.median}
unit={`млн ₽ · ${marketDeals.kpi.delta}`}
unitColor={tokens.success}
/>
<KpiCell
label="ДИАПАЗОН"
value={marketDeals.kpi.range}
unit="млн"
valueColor={tokens.accent}
last
/>
</div>
<div
style={{
...tableHead,
display: "grid",
gridTemplateColumns: DEAL_GRID,
}}
>
<span>АДРЕС</span>
<span>ИСТОЧНИК</span>
<span>/М²</span>
<span>ЦЕНА СДЕЛКИ</span>
<span>ДАТА</span>
</div>
{dealRows.map((r: DealRow, i) => (
<div
key={i}
className="sv-row"
style={{
...tableRow,
display: "grid",
gridTemplateColumns: DEAL_GRID,
}}
>
<span style={{ color: tokens.body }}>
{r.addr}
<span style={metaSpan}>
{" · "}
{r.meta}
</span>
</span>
<span>
<span style={badge}>Росреестр</span>{" "}
<span style={{ fontSize: "9px", color: tokens.muted2 }}>
по улице
</span>
</span>
<span style={monoMuted}>{r.ppm}</span>
<span style={monoInk}>{r.price}</span>
<span style={monoMuted2}>01.01.26</span>
</div>
))}
<div
style={{
padding: "11px 18px",
fontSize: "10px",
color: tokens.hint,
background: KPI_BG,
}}
>
Показано 10 фактических сделок
</div>
</div>
</div>
);
}