From 3cea915e4820d7d1fa2c5b939eeb3cce07ff8616 Mon Sep 17 00:00:00 2001 From: Light1YT Date: Thu, 4 Jun 2026 12:50:37 +0500 Subject: [PATCH] feat(site-finder): clickable parcel markers open analysis from the map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Marker click opens an interactive Leaflet popup with a primary "Открыть анализ" action that navigates to /site-finder/analysis/ (encodeURIComponent — matches the [cad] route's single-decode contract) and records the visit in RecentParcels. Hover tooltip enriched with status + actionable hint. Existing ParcelDrawer preview kept, reachable via the popup's secondary "Подробнее". ESLint + tsc clean. --- frontend/src/app/site-finder/page.tsx | 18 +++- .../components/site-finder/entry/EntryMap.tsx | 98 ++++++++++++++++--- 2 files changed, 104 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/site-finder/page.tsx b/frontend/src/app/site-finder/page.tsx index 20a08064..f2a98d73 100644 --- a/frontend/src/app/site-finder/page.tsx +++ b/frontend/src/app/site-finder/page.tsx @@ -10,7 +10,10 @@ import { MapFilterBar } from "@/components/site-finder/entry/MapFilterBar"; import { ParcelDrawer } from "@/components/site-finder/entry/ParcelDrawer"; import { ParcelLegend } from "@/components/site-finder/entry/ParcelLegend"; import { RecentParcels } from "@/components/site-finder/entry/RecentParcels"; -import { useParcelsBboxQuery } from "@/lib/site-finder-api"; +import { + addLocalRecentParcel, + useParcelsBboxQuery, +} from "@/lib/site-finder-api"; import type { ParcelBboxFilters, ParcelBboxItem, @@ -85,6 +88,18 @@ export default function SiteFinderPage() { setSelectedParcel(null); } + function handleParcelOpen(parcel: ParcelBboxItem) { + // Record the visit so RecentParcels stays populated, then navigate. + addLocalRecentParcel({ + cad_num: parcel.cad_num, + address: parcel.address, + area_ha: parcel.area_ha, + district: parcel.district, + visited_at: new Date().toISOString(), + }); + router.push(`/site-finder/analysis/${encodeURIComponent(parcel.cad_num)}`); + } + function handleCadSubmit(cad: string) { router.push(`/site-finder/analysis/${encodeURIComponent(cad)}`); } @@ -162,6 +177,7 @@ export default function SiteFinderPage() { void; + /** Open the side drawer with a parcel preview (secondary action). */ onSelect: (parcel: ParcelBboxItem) => void; } -function ParcelMarkers({ parcels, selectedCad, onSelect }: ParcelMarkersProps) { +function ParcelMarkers({ + parcels, + selectedCad, + onOpen, + onSelect, +}: ParcelMarkersProps) { return ( <> {parcels.map((parcel) => { const color = STATUS_COLORS[parcel.status] ?? "#73767E"; + const statusLabel = STATUS_LABELS[parcel.status] ?? parcel.status; const isSelected = parcel.cad_num === selectedCad; return ( { - e.originalEvent.stopPropagation(); - onSelect(parcel); - }, - }} + // Leaflet renders interactive vector markers with a pointer cursor + // and keeps them keyboard-focusable; click opens the Popup below. > + {/* Hover hint — quick identity without committing to the analyze POST */}
{parcel.cad_num}
-
- {parcel.area_ha.toFixed(2)} га · {parcel.district} +
+ {parcel.area_ha.toFixed(2)} га · {statusLabel} +
+
+ Нажмите, чтобы открыть анализ
+ + {/* Click target — interactive popup with the primary navigate action */} + +
+
+ {parcel.cad_num} +
+
+ {parcel.area_ha.toFixed(2)} га · {statusLabel} +
+ + +
+
); })} @@ -118,6 +189,9 @@ const DEFAULT_ZOOM = 12; interface EntryMapProps { filters: ParcelBboxFilters; selectedCad: string | null; + /** Primary action: navigate to the analysis page for a parcel. */ + onParcelOpen: (parcel: ParcelBboxItem) => void; + /** Secondary action: open the preview drawer for a parcel. */ onParcelSelect: (parcel: ParcelBboxItem) => void; onParcelDeselect: () => void; /** Called whenever the map viewport changes — allows parent to read bbox for filter counters */ @@ -127,6 +201,7 @@ interface EntryMapProps { export function EntryMap({ filters, selectedCad, + onParcelOpen, onParcelSelect, onParcelDeselect, onBboxChange, @@ -163,6 +238,7 @@ export function EntryMap({ )}