194 lines
5.2 KiB
TypeScript
194 lines
5.2 KiB
TypeScript
"use client";
|
||
|
||
import type {
|
||
ParcelSuccessRecommendation,
|
||
SuccessRankingBucket,
|
||
} from "@/types/site-finder";
|
||
|
||
interface Props {
|
||
recommendation: ParcelSuccessRecommendation | null | undefined;
|
||
}
|
||
|
||
function successColor(score: number): string {
|
||
if (score > 0.5) return "#16a34a";
|
||
if (score >= 0) return "#d97706";
|
||
return "#6b7280";
|
||
}
|
||
|
||
function successBg(score: number): string {
|
||
if (score > 0.5) return "#dcfce7";
|
||
if (score >= 0) return "#fef3c7";
|
||
return "#f3f4f6";
|
||
}
|
||
|
||
function formatPrice(v: number | null): string {
|
||
if (v == null) return "—";
|
||
return (v / 1000).toFixed(0) + " тыс";
|
||
}
|
||
|
||
function formatArea(v: number | null): string {
|
||
if (v == null) return "—";
|
||
return v.toFixed(0) + " м²";
|
||
}
|
||
|
||
function SuccessBadge({ bucket }: { bucket: SuccessRankingBucket }) {
|
||
const color = successColor(bucket.success_score);
|
||
const bg = successBg(bucket.success_score);
|
||
return (
|
||
<div
|
||
style={{
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
gap: 6,
|
||
borderRadius: 6,
|
||
padding: "2px 8px",
|
||
background: bg,
|
||
border: `1px solid ${color}44`,
|
||
}}
|
||
>
|
||
<div
|
||
style={{
|
||
width: 36,
|
||
height: 8,
|
||
borderRadius: 4,
|
||
background: "#e5e7eb",
|
||
overflow: "hidden",
|
||
position: "relative",
|
||
}}
|
||
>
|
||
<div
|
||
style={{
|
||
position: "absolute",
|
||
left: 0,
|
||
top: 0,
|
||
height: "100%",
|
||
width: `${Math.max(0, Math.min(1, bucket.success_score)) * 100}%`,
|
||
background: color,
|
||
borderRadius: 4,
|
||
}}
|
||
/>
|
||
</div>
|
||
<span style={{ fontSize: 12, fontWeight: 600, color }}>
|
||
{bucket.success_score.toFixed(2)}
|
||
</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function SuccessRecommendationBlock({ recommendation }: Props) {
|
||
if (!recommendation || recommendation.ranking.length === 0) {
|
||
return (
|
||
<div
|
||
style={{
|
||
borderRadius: 10,
|
||
padding: "14px 16px",
|
||
background: "#f3f4f6",
|
||
color: "#6b7280",
|
||
fontSize: 13,
|
||
}}
|
||
>
|
||
Недостаточно сделок в районе для рейтинга (мин 30)
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const { district, ranking, top_bucket, note } = recommendation;
|
||
const top5 = ranking.slice(0, 5);
|
||
|
||
return (
|
||
<div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
|
||
<div style={{ fontSize: 14, fontWeight: 700, color: "#111827" }}>
|
||
Что хорошо продаётся в районе {district}
|
||
</div>
|
||
|
||
<div style={{ overflowX: "auto" }}>
|
||
<table
|
||
style={{
|
||
width: "100%",
|
||
borderCollapse: "collapse",
|
||
fontSize: 13,
|
||
}}
|
||
>
|
||
<thead>
|
||
<tr>
|
||
{[
|
||
"Бакет",
|
||
"Сделок",
|
||
"Ср. цена ₽/м²",
|
||
"Ср. площадь м²",
|
||
"Успех",
|
||
].map((h) => (
|
||
<th
|
||
key={h}
|
||
style={{
|
||
textAlign: "left",
|
||
fontSize: 11,
|
||
fontWeight: 600,
|
||
color: "#6b7280",
|
||
textTransform: "uppercase",
|
||
letterSpacing: "0.04em",
|
||
paddingBottom: 6,
|
||
paddingRight: 12,
|
||
whiteSpace: "nowrap",
|
||
borderBottom: "1px solid #e5e7eb",
|
||
}}
|
||
>
|
||
{h}
|
||
</th>
|
||
))}
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{top5.map((row) => {
|
||
const isTop = row.bucket === top_bucket.bucket;
|
||
return (
|
||
<tr key={row.bucket}>
|
||
<td
|
||
style={{
|
||
padding: "6px 12px 6px 0",
|
||
color: "#111827",
|
||
fontWeight: isTop ? 600 : 400,
|
||
whiteSpace: "nowrap",
|
||
}}
|
||
>
|
||
{isTop ? "⭐ " : ""}
|
||
{row.bucket}
|
||
</td>
|
||
<td
|
||
style={{
|
||
padding: "6px 12px 6px 0",
|
||
color: "#374151",
|
||
}}
|
||
>
|
||
{row.n_deals}
|
||
</td>
|
||
<td
|
||
style={{
|
||
padding: "6px 12px 6px 0",
|
||
color: "#374151",
|
||
}}
|
||
>
|
||
{formatPrice(row.avg_price_per_m2)}
|
||
</td>
|
||
<td
|
||
style={{
|
||
padding: "6px 12px 6px 0",
|
||
color: "#374151",
|
||
}}
|
||
>
|
||
{formatArea(row.avg_area_m2)}
|
||
</td>
|
||
<td style={{ padding: "6px 0 6px 0" }}>
|
||
<SuccessBadge bucket={row} />
|
||
</td>
|
||
</tr>
|
||
);
|
||
})}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div style={{ fontSize: 11, color: "#9ca3af" }}>{note}</div>
|
||
</div>
|
||
);
|
||
}
|