gendesign/tradein-mvp/frontend/src/components/trade-in/CianValuationCard.tsx
lekss361 08cf615ad7
Some checks failed
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 5s
Deploy Trade-In / build-backend (push) Successful in 48s
Deploy Trade-In / build-frontend (push) Has been cancelled
feat(tradein/cian): surface valuation chart + rent + change-pct on /trade-in (#530)
2026-05-24 14:32:54 +00:00

93 lines
3.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";
import type { CianValuationSummary } from "@/types/trade-in";
interface Props {
data: CianValuationSummary | null | undefined;
}
function formatRub(n: number | null | undefined): string {
if (n == null) return "—";
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(2)} млн ₽`;
return new Intl.NumberFormat("ru-RU").format(n) + " ₽";
}
function Sparkline({ points }: { points: Array<{ date: string; price: number }> }) {
if (points.length < 2) return null;
const W = 240, H = 60, PAD = 4;
const xs = points.map((_, i) => PAD + (i * (W - 2 * PAD)) / (points.length - 1));
const prices = points.map((p) => p.price);
const min = Math.min(...prices), max = Math.max(...prices);
const range = max - min || 1;
const ys = prices.map((p) => H - PAD - ((p - min) / range) * (H - 2 * PAD));
const d = xs.map((x, i) => `${i === 0 ? "M" : "L"}${x},${ys[i]}`).join(" ");
const firstDate = points[0].date.slice(0, 7); // "YYYY-MM"
const lastDate = points[points.length - 1].date.slice(0, 7);
return (
<div className="cian-valuation__sparkline">
<svg width={W} height={H} aria-hidden="true">
<path d={d} fill="none" stroke="currentColor" strokeWidth={1.5} />
</svg>
<div className="cian-valuation__sparkline-dates">
<span>{firstDate}</span>
<span>{lastDate}</span>
</div>
</div>
);
}
function ChangeBadge({
pct,
direction,
}: {
pct: number | null;
direction: CianValuationSummary["chart_change_direction"];
}) {
if (pct == null || direction == null) return null;
const arrow = direction === "increase" ? "↑" : direction === "decrease" ? "↓" : "—";
const colorClass =
direction === "increase"
? "cian-valuation__badge--up"
: direction === "decrease"
? "cian-valuation__badge--down"
: "cian-valuation__badge--neutral";
return (
<span className={`cian-valuation__badge ${colorClass}`}>
{arrow} {Math.abs(pct).toFixed(1)}%
</span>
);
}
export function CianValuationCard({ data }: Props) {
if (!data || data.chart.length === 0) return null;
return (
<section className="cian-valuation" aria-label="Оценка Cian">
<header className="cian-valuation__head">
<span className="cian-valuation__title">Оценка Cian</span>
<ChangeBadge pct={data.chart_change_pct} direction={data.chart_change_direction} />
</header>
<div className="cian-valuation__row">
<div className="cian-valuation__metric">
<div className="cian-valuation__metric-label">Продажа</div>
<div className="cian-valuation__metric-value cian-valuation__metric-value--primary">
{formatRub(data.sale_price_rub)}
</div>
</div>
{data.rent_price_rub != null && (
<div className="cian-valuation__metric">
<div className="cian-valuation__metric-label">Аренда / мес</div>
<div className="cian-valuation__metric-value">
{formatRub(data.rent_price_rub)}
</div>
</div>
)}
</div>
<Sparkline points={data.chart} />
</section>
);
}