fix(sf-fe): adapt by-bbox response shape + add CadInput on entry page

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).
This commit is contained in:
lekss361 2026-05-18 02:17:05 +03:00
parent fd4eb8c6f4
commit 839cf05d8b
2 changed files with 35 additions and 3 deletions

View file

@ -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<ParcelBboxFilters>({});
const [selectedParcel, setSelectedParcel] = useState<ParcelBboxItem | null>(
null,
@ -82,6 +85,10 @@ export default function SiteFinderPage() {
setSelectedParcel(null);
}
function handleCadSubmit(cad: string) {
router.push(`/site-finder/analysis/${encodeURIComponent(cad)}`);
}
return (
<main
style={{
@ -172,6 +179,7 @@ export default function SiteFinderPage() {
overflowY: "auto",
}}
>
<CadInput onSubmit={handleCadSubmit} loading={false} />
<RecentParcels />
<ParcelLegend />
</div>

View file

@ -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<ParcelBboxItem[]>(
`/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,