feat(sf-fe-a2): /site-finder entry — EntryMap + filters + drawer + recent #343
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#343
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/sf-fe-a2-entry"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
SF Frontend Wave 2 parallel со A4. Fill
site-finder/page.tsxshell (A1 merged) — карта-first entry с filters + drawer preview + recent list.Files
NEW components
frontend/src/components/site-finder/entry/:EntryMap.tsx— Leaflet карта ЕКБ с CircleMarker парцелей, bbox-aware refetch (TanStack Query), click-to-select, isFetching indicatorMapFilterBar.tsx— chip filters (status / area / vri) + district select + counter, bbox-linkedParcelDrawer.tsx— slide-in 360px: 4 KPI (status / area / district / vri) + coords + «Открыть анализ» →/site-finder/analysis/[cad], fallback ПКК РосреестрRecentParcels.tsx— TanStack Query + localStoragegd_recent_parcelsmerge (B2 server list + local-only)ParcelLegend.tsx— status color legend (free / in_progress / favorite) +STATUS_COLORSexport reused в EntryMapHooks
frontend/src/lib/site-finder-api.ts:useParcelsBboxQuery(bbox)— fetch B1, mock fallbackMOCK_PARCELS_BBOXuseRecentParcels()— fetch B2 + merge localStorage,getLocalRecentParcels/addLocalRecentParcelFixture
frontend/src/lib/mocks/parcels-bbox.json— 20 ЕКБ парцелей (6 районов, все статусы, разные ВРИ)Page wire
frontend/src/app/site-finder/page.tsx— replaced placeholders с real components.Mock toggle
USE_MOCKS=trueилиMOCK_PARCELS_BBOX=true→ fixture (20 парцелей), no backend needed for demo.Constraints
analysis/[cad]/page.tsx(A4),components/ui/*(A3)Part of plan vault
code/patterns/SiteFinder_Frontend_Migration_Plan_May18.md§A2.Deep Code Review — verdict: APPROVE
Files: 8 (1 modified, 7 new) · Lines: +1629 / -93 · Mergeable: yes · Prior reviews: none
Critical / High / Medium — none
Leaflet SSR safety — CORRECT
page.tsxusesdynamic(() => import("@/components/site-finder/entry/EntryMap").then(m => m.EntryMap), { ssr: false, loading: ... }). Combined with"use client"directive inEntryMap.tsx, this prevents the classic hydration crash fromwindowaccess inside Leaflet's module init. Loading fallback shows "Загрузка карты..." while the chunk downloads.Bbox refetch — ACCEPTABLE (follow-up worth)
BboxListenerhooks Leaflet'smoveend+zoomend(gesture-end events, not continuousmove), so requests fire once per pan/zoom — not in a loop. TanStack QuerystaleTime: 30_000+ structural query key (bbox + filters) prevents re-fetches when nothing changes. Query is gated byenabled: MOCK_PARCELS_BBOX || bbox != nullso no fetch withnullbbox.Minor nit (follow-up, not blocking):
FilterBarBridgefires twouseParcelsBboxQuerycalls (one with filters, one without) to computematchCount / totalCount. For prod B1 backend this doubles QPS per bbox change. Consider either (a) a single query + client-side count or (b) backend?count_only=1variant in a later PR. For mock period this is free.URL safety for cad_num — CORRECT
Both
ParcelDrawer.tsx:313andRecentParcels.tsx:154useencodeURIComponent(parcel.cad_num)when constructing/site-finder/analysis/{cad}paths and thepkk.rosreestr.ruexternal link. External anchor hastarget="_blank" rel="noopener noreferrer". NodangerouslySetInnerHTMLanywhere. No injection vector.Other findings
react-leaflet@^5.0.0andleaflet@^1.9.4already infrontend/package.json.localStorage: try/catch +Array.isArrayguard ingetLocalRecentParcels; LRU dedup-then-slice inaddLocalRecentParcel— atomic R-M-W is fine (single-tab single-thread). Per-item shape not validated (would leak malformed objects from corrupted storage). Low risk, document only.STATUS_COLORSimport path:EntryMapandParcelDrawerboth import from./ParcelLegend— no circular dep,ParcelLegendexports only constants + a self-contained component.any;unknowncast guarded; type-only imports used.MOCK_PARCELS_BBOXandMOCK_RECENT_PARCELSboth exist inmock-toggle.ts(verified).ParcelBboxItemschema.Recommendation
Merging now (squash).
Follow-up tickets worth filing (non-blocking):
useDebouncedValue(bbox, 250ms)if real-traffic bbox refetch chatter becomes visible.FilterBarBridgeonce B1 backend lands in prod.STATUS_COLORS: Record<ParcelStatus, string>instead ofRecord<string, string>.Merged via deep-code-reviewer — verdict APPROVE.
Scope: 8 files (5 NEW components in
components/site-finder/entry/, NEWlib/site-finder-api.tshooks, NEW fixture, wireapp/site-finder/page.tsx). +1629/-93 lines.Highlights:
any, no@ts-ignore. Single justifiedeslint-disable react-hooks/exhaustive-depsinBboxListenerinitial-emit effect.useEffect+fetchantipattern.queryKeystable per filter combo,staleTime30s / 60s reasonable.dynamic(..., { ssr: false })inpage.tsx.useMapEventshandles listener cleanup automatically.typeof window === "undefined"+ try/catch around JSON parse/write).encodeURIComponent(cad_num);target="_blank" rel="noopener noreferrer"correct. NoinnerHTML/dangerouslySetInnerHTML.MOCK_PARCELS_BBOXshort-circuits beforeapiFetch;useRecentParcelsgracefully falls back to localStorage on B2 endpoint failure.app/site-finder/analysis/**(A4) orcomponents/ui/**(A3) — no coupling with parallel PR #344.Non-blocking nits (consider in follow-up):
EntryMap.tsx:121—mapRefis set but never read (dead ref, can be removed).page.tsx:54-65—FilterBarBridgefires 2 distinct bbox queries (with/without filters) per bbox change. Fine for client-side mock filtering; a single count-aware B1 endpoint could collapse this later.applyFilters(site-finder-api.ts:108) treatsvri === "other"as match-all — undocumented escape hatch.RecentParcelsuses inlineonMouseEnter/Leavestyle mutation instead of CSS:hover— works, minor consistency item.Merge commit:
5aab8a97af3b6ee33c472719a96717ba153b8730.