fix(sf-fe): decode cad param (B5 400) + hide entry sidebar when drawer open #350
3 changed files with 59 additions and 20 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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) */}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue