From 4d1c409e18e0c4eaa50fad5dae210b6787b103e3 Mon Sep 17 00:00:00 2001 From: lekss361 Date: Sun, 24 May 2026 20:37:29 +0000 Subject: [PATCH] =?UTF-8?q?feat(tradein):=20=C2=AB=D0=9F=D0=BE=20=D0=B2?= =?UTF-8?q?=D0=B0=D1=88=D0=B5=D0=B9=20=D1=83=D0=BB=D0=B8=D1=86=D0=B5=C2=BB?= =?UTF-8?q?=20=E2=80=94=20StreetDealsCard=20=D0=BD=D0=B0=20real=20=D0=94?= =?UTF-8?q?=D0=9A=D0=9F-=D1=81=D0=B4=D0=B5=D0=BB=D0=BA=D0=B8=20=D0=A0?= =?UTF-8?q?=D0=BE=D1=81=D1=80=D0=B5=D0=B5=D1=81=D1=82=D1=80=D0=B0=20(#556)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tradein-mvp/frontend/src/app/page.tsx | 2 + .../components/trade-in/StreetDealsCard.tsx | 134 ++++++++++++++++++ tradein-mvp/frontend/src/lib/trade-in-api.ts | 24 ++++ tradein-mvp/frontend/src/types/trade-in.ts | 14 ++ 4 files changed, 174 insertions(+) create mode 100644 tradein-mvp/frontend/src/components/trade-in/StreetDealsCard.tsx diff --git a/tradein-mvp/frontend/src/app/page.tsx b/tradein-mvp/frontend/src/app/page.tsx index 83407069..5b62b253 100644 --- a/tradein-mvp/frontend/src/app/page.tsx +++ b/tradein-mvp/frontend/src/app/page.tsx @@ -23,6 +23,7 @@ import { HouseAnalyticsSection } from "@/components/trade-in/HouseAnalyticsSecti import { PhotoUpload } from "@/components/trade-in/PhotoUpload"; import { ListingsCard } from "@/components/trade-in/ListingsCard"; import { DealsCard } from "@/components/trade-in/DealsCard"; +import { StreetDealsCard } from "@/components/trade-in/StreetDealsCard"; import { OfferCard } from "@/components/trade-in/OfferCard"; import { TestPresets } from "@/components/trade-in/TestPresets"; import { useEstimateImvBenchmark, useEstimateHouses } from "@/lib/trade-in-api"; @@ -182,6 +183,7 @@ export default function TradeInPage() { + ) : ( diff --git a/tradein-mvp/frontend/src/components/trade-in/StreetDealsCard.tsx b/tradein-mvp/frontend/src/components/trade-in/StreetDealsCard.tsx new file mode 100644 index 00000000..59bec39c --- /dev/null +++ b/tradein-mvp/frontend/src/components/trade-in/StreetDealsCard.tsx @@ -0,0 +1,134 @@ +"use client"; + +/** + * StreetDealsCard — «По вашей улице» — ДКП-сделки Росреестра по улице target адреса. + * Питается от GET /api/v1/trade-in/street-deals (PR #555). + * Не рендерится если street не извлёкся или count = 0. + */ +import { useStreetDeals } from "@/lib/trade-in-api"; +import { openRosreestrWithAddress } from "@/lib/rosreestr"; +import type { AggregatedEstimate, AnalogLot } from "@/types/trade-in"; + +interface Props { + estimate: AggregatedEstimate; +} + +function fmtRub(v: number): string { + return v.toLocaleString("ru-RU"); +} + +function fmtDate(iso: string | null): string { + if (!iso) return "—"; + try { + return new Date(iso).toLocaleDateString("ru-RU", { + day: "2-digit", + month: "2-digit", + year: "2-digit", + }); + } catch { + return "—"; + } +} + +export function StreetDealsCard({ estimate }: Props) { + const { data, isLoading, isError } = useStreetDeals( + estimate.target_address ?? null, + estimate.area_m2 ?? null, + estimate.rooms ?? null, + ); + + if (isLoading || isError) return null; + if (!data || !data.street || data.count === 0) return null; + + const rangeLowM = (data.range_low_rub / 1_000_000).toFixed(1); + const rangeHighM = (data.range_high_rub / 1_000_000).toFixed(1); + + return ( +
+
+
+
Секция · По вашей улице
+

ДКП-сделки на улице {data.street}

+
+
+
+ Период: 12 мес +
+
источник: Росреестр (открытые данные)
+
+
+ +
+
+
Сделок за 12 мес
+
+ {data.count} + шт +
+
+
+
Медиана
+
+ {fmtRub(data.median_price_per_m2)} + ₽/м² +
+
+
+
Диапазон
+
+ + {rangeLowM} – {rangeHighM} + + млн ₽ +
+
+
+ + + + + + + + + + + + + {data.deals.map((d: AnalogLot, i: number) => ( + + + + + + + + + ))} + +
АдресПлощадьЦена₽/м²Дата +
+ + {d.address} + + {d.area_m2.toFixed(1)} м²{fmtRub(d.price_rub)} ₽{fmtRub(d.price_per_m2)}{fmtDate(d.listing_date)} + +
+ +
+ + Показано{" "} + {Math.min(data.deals.length, 10)}{" "} + из {data.count} сделок + +
+
+ ); +} diff --git a/tradein-mvp/frontend/src/lib/trade-in-api.ts b/tradein-mvp/frontend/src/lib/trade-in-api.ts index 3536f2bb..818f212a 100644 --- a/tradein-mvp/frontend/src/lib/trade-in-api.ts +++ b/tradein-mvp/frontend/src/lib/trade-in-api.ts @@ -11,6 +11,7 @@ import type { IMVBenchmarkResponse, PlacementHistoryItem, SellTimeSensitivityResponse, + StreetDealsResponse, TradeInEstimateInput, } from "@/types/trade-in"; @@ -120,6 +121,29 @@ export function useEstimateHouseAnalytics(estimate_id: string | null) { }); } +/** + * GET /api/v1/trade-in/street-deals + * ДКП-сделки Росреестра по улице target адреса. Per-street (open dataset + * без номера дома). Empty response если street не извлёкся. + */ +export function useStreetDeals( + address: string | null, + area_m2: number | null, + rooms: number | null, +) { + const params = new URLSearchParams(); + if (address) params.set("address", address); + if (area_m2 !== null) params.set("area_m2", String(area_m2)); + if (rooms !== null) params.set("rooms", String(rooms)); + return useQuery({ + queryKey: ["trade-in", "street-deals", address, area_m2, rooms], + queryFn: () => + apiFetch(`${BASE}/street-deals?${params}`), + enabled: !!address && area_m2 !== null && rooms !== null, + staleTime: 5 * 60_000, + }); +} + /** * GET /api/v1/trade-in/estimate/{id}/sell-time-sensitivity * Median exposure days bucketed by price premium (-5%, 0, +5%, +10%). diff --git a/tradein-mvp/frontend/src/types/trade-in.ts b/tradein-mvp/frontend/src/types/trade-in.ts index 6d1d5822..2766bcc9 100644 --- a/tradein-mvp/frontend/src/types/trade-in.ts +++ b/tradein-mvp/frontend/src/types/trade-in.ts @@ -192,6 +192,20 @@ export interface HouseAnalyticsResponse { kpi: HouseAnalyticsKpi; } +// ── Street deals (endpoint: GET /trade-in/street-deals) ── + +export interface StreetDealsResponse { + street: string | null; // "Космонавтов" / null если не извлёкся + period_from: string; // ISO date "2025-05-29" + period_to: string; + count: number; // all matching, не топ-10 + median_price_rub: number; + median_price_per_m2: number; + range_low_rub: number; + range_high_rub: number; + deals: AnalogLot[]; // top-10 по deal_date DESC +} + // ── Sell-time sensitivity (endpoint: GET /estimate/{id}/sell-time-sensitivity) ── export interface SellTimeBucket {