gendesign/tradein-mvp/frontend/src/components/trade-in/PriceHistoryChart.tsx
bot-backend 27766acf38
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(trade-in/ui): расшифровка ДКП/IMV/CV + графики на --viz токены + убрать emoji из вердиктов (#1871)
- HeroSummary: все вхождения ДКП/IMV/CV обёрнуты в <abbr title="..."> с расшифровкой
- IMVBenchmark: «Avito IMV» → abbr с title
- HeroSummary/HeroTransparency: emoji ⚠️ заменены на span-точки var(--warn) + текст
- PriceHistoryChart: stroke="#2563eb"/"#ea580c" → var(--viz-1)/var(--viz-4)
- PriceRangeBar: "#bfdbfe"/"#1d4ed8" → color-mix(--viz-1)/var(--viz-1)
- SellTimeSensitivity: hardcoded bg-цвета → --success-soft/--accent-soft/--warn-soft/--danger-soft; border → var(--viz-1)
- MapCard: пины обновлены на hex-значения --viz-1/--viz-3/--viz-4 (Leaflet JS, не CSS-контекст)
- MapPicker: комментарий -- viz-1 hex (Leaflet constraint)
2026-06-26 19:16:55 +03:00

102 lines
3.2 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";
import { useMemo } from "react";
import {
LineChart,
Line,
XAxis,
YAxis,
Tooltip,
Legend,
ResponsiveContainer,
CartesianGrid,
} from "recharts";
import type { PriceHistoryYearPoint } from "@/types/trade-in";
type Props = { points: PriceHistoryYearPoint[] };
const fmtK = (n: number) => `${Math.round(n / 1000)}k`;
interface PivotRow {
year: number;
avito_imv?: number;
yandex_valuation?: number;
n_avito?: number;
n_yandex?: number;
}
export function PriceHistoryChart({ points }: Props) {
const pivoted = useMemo<PivotRow[]>(() => {
const byYear: Record<number, PivotRow> = {};
for (const p of points) {
if (!byYear[p.year]) byYear[p.year] = { year: p.year };
if (p.source === "avito_imv") {
byYear[p.year].avito_imv = p.median_price_per_m2;
byYear[p.year].n_avito = p.n_lots;
} else if (p.source === "yandex_valuation") {
byYear[p.year].yandex_valuation = p.median_price_per_m2;
byYear[p.year].n_yandex = p.n_lots;
}
}
return Object.values(byYear).sort((a, b) => a.year - b.year);
}, [points]);
const totalLots = points.reduce((s, p) => s + p.n_lots, 0);
return (
<article className="card" style={{ marginTop: 12, padding: 16 }}>
<header style={{ marginBottom: 8 }}>
<h4 style={{ margin: 0, fontSize: 14, fontWeight: 600 }}>
История цен в этом доме
</h4>
<small style={{ color: "var(--muted, #6b7280)" }}>
Медиана /м² по годам · только этот дом · Avito + Яндекс ({totalLots} лотов)
</small>
</header>
{/* #835: декоративный чарт — aria-hidden (данные в тексте/легенде карточки). */}
<div aria-hidden="true" style={{ width: "100%", height: 240 }}>
<ResponsiveContainer>
<LineChart
data={pivoted}
margin={{ top: 8, right: 16, left: 8, bottom: 8 }}
>
<CartesianGrid stroke="#eee" strokeDasharray="3 3" />
<XAxis dataKey="year" tick={{ fontSize: 11 }} />
<YAxis tickFormatter={fmtK} tick={{ fontSize: 11 }} />
<Tooltip
formatter={(value: number, name: string) =>
[
`${value.toLocaleString("ru-RU")} ₽/м²`,
name === "avito_imv"
? "Avito"
: name === "yandex_valuation"
? "Яндекс"
: name,
]
}
labelFormatter={(year) => `${year} год`}
/>
<Legend wrapperStyle={{ fontSize: 11 }} />
<Line
type="monotone"
dataKey="avito_imv"
stroke="var(--viz-1)"
strokeWidth={2}
dot={{ r: 3 }}
name="Avito"
connectNulls
/>
<Line
type="monotone"
dataKey="yandex_valuation"
stroke="var(--viz-4)"
strokeWidth={2}
dot={{ r: 3 }}
name="Яндекс"
connectNulls
/>
</LineChart>
</ResponsiveContainer>
</div>
</article>
);
}