gendesign/frontend/src/components/site-finder/SuccessRecommendationBlock.tsx

194 lines
5.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 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>
);
}