93 lines
3.1 KiB
TypeScript
93 lines
3.1 KiB
TypeScript
"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>
|
||
);
|
||
}
|