From 98323027aae2675363bc2fa316d1cfac764f151b Mon Sep 17 00:00:00 2001 From: lekss361 Date: Sun, 17 May 2026 13:41:15 +0300 Subject: [PATCH] fix(sf-03): CompetitorTable Status column + filter tabs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit После backend PR #276 (site_status в /analyze) — frontend показывает статус строящиеся/сданные с цветным badge и фильтр-tabs наверху таблицы. TS type extended manually (backward compat) — codegen подхватит после backend deploy. Closes (epic part) #271 item 3 --- .../site-finder/CompetitorTable.tsx | 135 +++++++++++++++++- frontend/src/types/site-finder.ts | 4 + 2 files changed, 135 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/site-finder/CompetitorTable.tsx b/frontend/src/components/site-finder/CompetitorTable.tsx index 3d0fbd1f..dff8cb49 100644 --- a/frontend/src/components/site-finder/CompetitorTable.tsx +++ b/frontend/src/components/site-finder/CompetitorTable.tsx @@ -1,7 +1,10 @@ "use client"; +import { useState } from "react"; import type { ParcelAnalysisCompetitor } from "@/types/site-finder"; +type StatusFilter = "all" | "building" | "ready"; + interface Props { competitors: ParcelAnalysisCompetitor[]; districtName: string | null | undefined; @@ -15,8 +18,94 @@ const CLASS_COLORS: Record = { Премиум: "#b45309", }; +function StatusBadge({ status }: { status: string | null | undefined }) { + if (status === "Строящиеся") { + return ( + + Строящиеся + + ); + } + if (status === "Сданные") { + return ( + + Сданные + + ); + } + return ( + + — + + ); +} + export function CompetitorTable({ competitors, districtName }: Props) { - const top20 = competitors.slice(0, 20); + const [filter, setFilter] = useState("all"); + + const buildingCount = competitors.filter( + (c) => c.site_status === "Строящиеся", + ).length; + const readyCount = competitors.filter( + (c) => c.site_status === "Сданные", + ).length; + + const filtered = competitors.filter((c) => { + if (filter === "building") return c.site_status === "Строящиеся"; + if (filter === "ready") return c.site_status === "Сданные"; + return true; + }); + + const top20 = filtered.slice(0, 20); + + const tabBase = { + padding: "4px 12px", + borderRadius: 6, + fontSize: 12, + fontWeight: 500, + cursor: "pointer", + border: "1px solid transparent", + background: "transparent", + color: "#6b7280", + transition: "all 0.15s", + } as const; + + const tabActive = { + ...tabBase, + background: "#fff", + border: "1px solid #d1d5db", + color: "#111827", + fontWeight: 600, + } as const; return (
+ {/* Header */}
+ {/* Filter tabs */} +
+ + + +
+ {top20.length === 0 ? (
- Конкурентов не найдено + {filtered.length === 0 && competitors.length > 0 + ? "Нет конкурентов по выбранному фильтру" + : "Конкурентов не найдено"}
) : (
@@ -66,6 +189,7 @@ export function CompetitorTable({ competitors, districtName }: Props) { "ЖК", "Девелопер", "Класс", + "Статус", "Квартир", "Район", "Расст., м", @@ -145,6 +269,9 @@ export function CompetitorTable({ competitors, districtName }: Props) { )} + + +
)} - {competitors.length > 20 && ( + {filtered.length > 20 && (
- Показано 20 из {competitors.length} ближайших ЖК + Показано 20 из {filtered.length} ближайших ЖК
)}
diff --git a/frontend/src/types/site-finder.ts b/frontend/src/types/site-finder.ts index 7217fd6b..9cbaef54 100644 --- a/frontend/src/types/site-finder.ts +++ b/frontend/src/types/site-finder.ts @@ -39,6 +39,10 @@ export interface ParcelAnalysisCompetitor { flat_count: number | null; district_name: string | null; distance_m: number; + // Added manually — backend PR #276 (site_status in /analyze). + // Codegen will overwrite after backend deploy (additive, backward compat). + site_status?: string | null; // 'Строящиеся' | 'Сданные' | null + ready_dt?: string | null; // ISO date } export interface ParcelAnalysisDistrict {