fix(sf-fe): decode cad param (B5 400) + hide entry sidebar when drawer open #350

Closed
lekss361 wants to merge 2 commits from fix/sf-cad-decode-drawer-overlay into main
3 changed files with 59 additions and 20 deletions

View file

@ -75,8 +75,13 @@ interface PageProps {
} }
export default function AnalysisPage({ params }: PageProps) { export default function AnalysisPage({ params }: PageProps) {
// React 19: use() unwraps Promise params (app router pattern) // React 19: use() unwraps Promise params (app router pattern).
const { cad } = use(params); // 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 ( return (
<main <main

View file

@ -2,8 +2,10 @@
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState } from "react"; import { useState } from "react";
import { CadInput } from "@/components/site-finder/CadInput";
import { MapFilterBar } from "@/components/site-finder/entry/MapFilterBar"; import { MapFilterBar } from "@/components/site-finder/entry/MapFilterBar";
import { ParcelDrawer } from "@/components/site-finder/entry/ParcelDrawer"; import { ParcelDrawer } from "@/components/site-finder/entry/ParcelDrawer";
import { ParcelLegend } from "@/components/site-finder/entry/ParcelLegend"; import { ParcelLegend } from "@/components/site-finder/entry/ParcelLegend";
@ -68,6 +70,7 @@ function FilterBarBridge({ filters, onChange, bbox }: FilterBarBridgeProps) {
// ── Page ────────────────────────────────────────────────────────────────────── // ── Page ──────────────────────────────────────────────────────────────────────
export default function SiteFinderPage() { export default function SiteFinderPage() {
const router = useRouter();
const [filters, setFilters] = useState<ParcelBboxFilters>({}); const [filters, setFilters] = useState<ParcelBboxFilters>({});
const [selectedParcel, setSelectedParcel] = useState<ParcelBboxItem | null>( const [selectedParcel, setSelectedParcel] = useState<ParcelBboxItem | null>(
null, null,
@ -82,6 +85,10 @@ export default function SiteFinderPage() {
setSelectedParcel(null); setSelectedParcel(null);
} }
function handleCadSubmit(cad: string) {
router.push(`/site-finder/analysis/${encodeURIComponent(cad)}`);
}
return ( return (
<main <main
style={{ style={{
@ -160,21 +167,24 @@ export default function SiteFinderPage() {
/> />
</div> </div>
{/* Right sidebar */} {/* Right sidebar — hidden when ParcelDrawer is open to avoid overlap */}
<div {selectedParcel == null && (
style={{ <div
width: 300, style={{
flexShrink: 0, width: 300,
padding: "16px 16px 16px 0", flexShrink: 0,
display: "flex", padding: "16px 16px 16px 0",
flexDirection: "column", display: "flex",
gap: 12, flexDirection: "column",
overflowY: "auto", gap: 12,
}} overflowY: "auto",
> }}
<RecentParcels /> >
<ParcelLegend /> <CadInput onSubmit={handleCadSubmit} loading={false} />
</div> <RecentParcels />
<ParcelLegend />
</div>
)}
</div> </div>
{/* Parcel drawer (slide-in from right) */} {/* Parcel drawer (slide-in from right) */}

View file

@ -173,9 +173,33 @@ export function useParcelsBboxQuery(
if (filters.district) params.set("district", filters.district); if (filters.district) params.set("district", filters.district);
if (filters.vri) params.set("vri", filters.vri); if (filters.vri) params.set("vri", filters.vri);
return apiFetch<ParcelBboxItem[]>( // Backend returns { parcels, count, limit, bbox_area_km2 } with field
`/api/v1/parcels/by-bbox?${params.toString()}`, // 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, enabled: MOCK_PARCELS_BBOX || bbox != null,
staleTime: 30_000, staleTime: 30_000,