From 839cf05d8b2a32c0d3a790a3787af8a12c95ec5e Mon Sep 17 00:00:00 2001 From: lekss361 Date: Mon, 18 May 2026 02:17:05 +0300 Subject: [PATCH 1/2] fix(sf-fe): adapt by-bbox response shape + add CadInput on entry page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two prod regressions noticed by user after #347 deploy ('не видно участков · и нельзя просто ввести'): 1. Markers invisible — backend B1 returns { parcels: [{cad_num, centroid_lat, centroid_lon, area_m2, status, ...}], count, limit, bbox_area_km2 } but useParcelsBboxQuery typed the response as a flat ParcelBboxItem[] with fields { lat, lon, area_ha, district, vri, address, status }. Result: .map() iterated over an object (no markers rendered) and even if iterated, every CircleMarker got center=[undefined, undefined]. Added an adapter in queryFn: unwrap .parcels, rename centroid_lat/centroid_lon -> lat/lon, convert area_m2 -> area_ha, default status to 'free' when null, placeholder district/vri/address until B1 enrichment lands. applyFilters still runs client-side. 2. No manual cad entry — A2 #343 dropped the existing CadInput component from the entry page when replacing placeholders. Re-wired it in the right sidebar above RecentParcels; submit -> router.push to /site-finder/analysis/{cad} (matches A1 routing scheme). --- frontend/src/app/site-finder/page.tsx | 8 +++++++ frontend/src/lib/site-finder-api.ts | 30 ++++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/site-finder/page.tsx b/frontend/src/app/site-finder/page.tsx index 3fe11222..ea0a68f2 100644 --- a/frontend/src/app/site-finder/page.tsx +++ b/frontend/src/app/site-finder/page.tsx @@ -2,8 +2,10 @@ import dynamic from "next/dynamic"; import Link from "next/link"; +import { useRouter } from "next/navigation"; import { useState } from "react"; +import { CadInput } from "@/components/site-finder/CadInput"; import { MapFilterBar } from "@/components/site-finder/entry/MapFilterBar"; import { ParcelDrawer } from "@/components/site-finder/entry/ParcelDrawer"; import { ParcelLegend } from "@/components/site-finder/entry/ParcelLegend"; @@ -68,6 +70,7 @@ function FilterBarBridge({ filters, onChange, bbox }: FilterBarBridgeProps) { // ── Page ────────────────────────────────────────────────────────────────────── export default function SiteFinderPage() { + const router = useRouter(); const [filters, setFilters] = useState({}); const [selectedParcel, setSelectedParcel] = useState( null, @@ -82,6 +85,10 @@ export default function SiteFinderPage() { setSelectedParcel(null); } + function handleCadSubmit(cad: string) { + router.push(`/site-finder/analysis/${encodeURIComponent(cad)}`); + } + return (
+ diff --git a/frontend/src/lib/site-finder-api.ts b/frontend/src/lib/site-finder-api.ts index c0a6a70f..920ab23f 100644 --- a/frontend/src/lib/site-finder-api.ts +++ b/frontend/src/lib/site-finder-api.ts @@ -173,9 +173,33 @@ export function useParcelsBboxQuery( if (filters.district) params.set("district", filters.district); if (filters.vri) params.set("vri", filters.vri); - return apiFetch( - `/api/v1/parcels/by-bbox?${params.toString()}`, - ); + // Backend returns { parcels, count, limit, bbox_area_km2 } with field + // names centroid_lat/centroid_lon/area_m2 and nullable status. Adapt to + // the flat ParcelBboxItem[] shape the map components consume. district / + // vri / address are not in B1 yet — defaulted until enrichment lands. + const raw = await apiFetch<{ + parcels: Array<{ + cad_num: string; + centroid_lat: number; + centroid_lon: number; + area_m2: number | null; + land_category: string | null; + status: ParcelStatus | null; + }>; + }>(`/api/v1/parcels/by-bbox?${params.toString()}`); + + const adapted: ParcelBboxItem[] = raw.parcels.map((p) => ({ + cad_num: p.cad_num, + lat: p.centroid_lat, + lon: p.centroid_lon, + area_ha: p.area_m2 != null ? p.area_m2 / 10000 : 0, + status: p.status ?? "free", + district: "—", + vri: "other", + address: "", + })); + // Apply filters client-side until backend supports them (B1 follow-up) + return applyFilters(adapted, filters); }, enabled: MOCK_PARCELS_BBOX || bbox != null, staleTime: 30_000, -- 2.45.3 From 5b215def23a7153ee89f1aaebc0af18c21f0f7b1 Mon Sep 17 00:00:00 2001 From: lekss361 Date: Mon, 18 May 2026 02:30:45 +0300 Subject: [PATCH 2/2] fix(sf-fe): decode cad URL param + hide entry sidebar when drawer open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two prod issues: 1. Analysis page B5 endpoint returned HTTP 400 'Неверный формат кадастрового номера' for any cad. Next.js dynamic route delivers `params.cad` URL-encoded (':' -> '%3A'). Hooks then ran encodeURIComponent on the already-encoded string, producing /api/v1/parcels/66%253A41%253A0204016%253A10/analyze; FastAPI decoded one layer to '66%3A41%3A0204016%3A10', which the regex rejected. Fix: decodeURIComponent the param once at the page boundary so downstream consumers (hooks + breadcrumb + Section1) work with the canonical '66:41:0204016:10' and encode exactly once when building URLs. Also resolves the breadcrumb double- encode noted in PR #346 QA. 2. Entry page right-sidebar (CadInput + RecentParcels + ParcelLegend) visually overlapped the ParcelDrawer slide-in: both were anchored to the right edge with the drawer rendered above as an overlay without an opaque backdrop on the sidebar area. Conditional render: sidebar only mounts when no parcel is selected, so opening a parcel gives the drawer a clean right column. --- .../app/site-finder/analysis/[cad]/page.tsx | 9 +++-- frontend/src/app/site-finder/page.tsx | 34 ++++++++++--------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/frontend/src/app/site-finder/analysis/[cad]/page.tsx b/frontend/src/app/site-finder/analysis/[cad]/page.tsx index e73f256d..4b804421 100644 --- a/frontend/src/app/site-finder/analysis/[cad]/page.tsx +++ b/frontend/src/app/site-finder/analysis/[cad]/page.tsx @@ -75,8 +75,13 @@ interface PageProps { } export default function AnalysisPage({ params }: PageProps) { - // React 19: use() unwraps Promise params (app router pattern) - const { cad } = use(params); + // React 19: use() unwraps Promise params (app router pattern). + // Next.js delivers `cad` URL-encoded (":" -> "%3A"); decode once here so + // downstream hooks/components see the canonical "66:41:0204016:10" and can + // re-encode exactly once when building API URLs / hrefs (prevents double- + // encode that made backend regex reject the cad with HTTP 400). + const { cad: cadRaw } = use(params); + const cad = decodeURIComponent(cadRaw); return (
- {/* Right sidebar */} -
- - - -
+ {/* Right sidebar — hidden when ParcelDrawer is open to avoid overlap */} + {selectedParcel == null && ( +
+ + + +
+ )} {/* Parcel drawer (slide-in from right) */} -- 2.45.3