All checks were successful
CI / changes (push) Successful in 7s
CI / backend-tests (push) Has been skipped
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (push) Successful in 54s
CI / frontend-tests (pull_request) Successful in 54s
ZouitLayer (GeoJSON-полигоны из geom_geojson, severity blocker/warning/info по keyword, popup тип+имя) + ZouitLayerControlPanel (collapsible toggle per-severity, счётчик mappable) + SiteMap integration (default blocker+warning ON, info OFF, фон под POI). Graceful: нет geom_geojson → слой/панель скрыты, текстовый NspdZouitOverlapsBlock остаётся. 10 vitest. Closes #255
113 lines
4.4 KiB
TypeScript
113 lines
4.4 KiB
TypeScript
"use client";
|
||
|
||
/**
|
||
* MiniMap — compact Leaflet map (≈400px wide) showing parcel polygon + top POI.
|
||
* Dynamic import of react-leaflet (ssr:false) to avoid SSR breakage.
|
||
* Wraps SiteMap with constrained width for Section 1 layout.
|
||
*
|
||
* #1218: ранее обёртка имела `height: 320 + overflow: hidden` — это клипало
|
||
* legend + CpLayerControlPanel, рендерящиеся flow-div ПОД картой в SiteMap,
|
||
* и делало недостижимыми переключатели рыночных слоёв (Будущие проекты,
|
||
* Зоны риска, Перспективные ЗУ, Красные линии). Фикс: задаём только ширину,
|
||
* высота тайла карты — через prop `mapHeight` SiteMap; контент течёт вниз.
|
||
*/
|
||
|
||
import dynamic from "next/dynamic";
|
||
import type { ParcelAnalyzeResponse } from "@/lib/site-finder-api";
|
||
import type { ParcelAnalysis } from "@/types/site-finder";
|
||
import type { Geometry } from "geojson";
|
||
|
||
// MiniMap высота тайла карты — компромисс между плотностью Section 1 и
|
||
// читаемостью. Layer-controls/легенда вне этого размера (flow ниже).
|
||
const MINIMAP_HEIGHT = 280;
|
||
|
||
// Lazy-mount to avoid SSR breakage (Leaflet uses window)
|
||
const SiteMap = dynamic(
|
||
() =>
|
||
import("@/components/site-finder/SiteMap").then((m) => ({
|
||
default: m.SiteMap,
|
||
})),
|
||
{
|
||
ssr: false,
|
||
loading: () => (
|
||
<div
|
||
style={{
|
||
height: MINIMAP_HEIGHT,
|
||
background: "var(--bg-card-alt)",
|
||
borderRadius: 12,
|
||
border: "1px solid var(--border-card)",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
color: "var(--fg-tertiary)",
|
||
fontSize: 13,
|
||
}}
|
||
>
|
||
Загрузка карты...
|
||
</div>
|
||
),
|
||
},
|
||
);
|
||
|
||
interface Props {
|
||
data: ParcelAnalyzeResponse;
|
||
}
|
||
|
||
/**
|
||
* Adapts ParcelAnalyzeResponse → ParcelAnalysis shape expected by SiteMap.
|
||
* SiteMap reads: cad_num, geom_geojson, score_breakdown +
|
||
* #999 (958-B4): competitors, pipeline_24mo, nspd_risk_zones +
|
||
* §12.1-13 (#958): nspd_opportunity_parcels, nspd_red_lines (рыночные слои).
|
||
* Остальные required-поля заполняются безопасными дефолтами.
|
||
*/
|
||
function toSiteMapData(data: ParcelAnalyzeResponse): ParcelAnalysis {
|
||
return {
|
||
cad_num: data.cad_num,
|
||
source: data.source === "cad_building" ? "cad_building" : "cad_quarter",
|
||
geom_geojson: (data.geom_geojson as Geometry) ?? null,
|
||
score_breakdown: data.score_breakdown,
|
||
score: data.score,
|
||
poi_count: data.poi_count,
|
||
// #999 — пробрасываем рыночные данные в SiteMap-слои.
|
||
competitors: data.competitors ?? [],
|
||
pipeline_24mo: data.pipeline_24mo ?? undefined,
|
||
nspd_risk_zones: data.nspd_risk_zones ?? undefined,
|
||
// §12.1-13 (#958) — opportunity-ЗУ + красные линии застройки.
|
||
nspd_opportunity_parcels: data.nspd_opportunity_parcels ?? undefined,
|
||
nspd_red_lines: data.nspd_red_lines ?? undefined,
|
||
// #255 — ЗОУИТ overlaps (geom_geojson) для map-слоя охранных зон.
|
||
nspd_zouit_overlaps: data.nspd_zouit_overlaps ?? undefined,
|
||
noise: null,
|
||
air_quality: null,
|
||
wind: null,
|
||
district: data.district ?? null,
|
||
};
|
||
}
|
||
|
||
export function MiniMap({ data }: Props) {
|
||
const adapted = toSiteMapData(data);
|
||
|
||
// #1218: НЕ задаём height + overflow:hidden на обёртке. SiteMap сам
|
||
// ограничивает высоту карты-тайла (mapHeight), а легенда + layer-controls
|
||
// (Будущие проекты / Зоны риска / Перспективные ЗУ / Красные линии) лежат
|
||
// под картой потоком и должны быть видны/кликабельны.
|
||
return (
|
||
<div
|
||
style={{
|
||
width: "100%",
|
||
maxWidth: 400,
|
||
flexShrink: 0,
|
||
}}
|
||
>
|
||
<SiteMap
|
||
data={adapted}
|
||
competitors={data.competitors ?? []}
|
||
pipelineObjects={data.pipeline_24mo?.top_objects ?? []}
|
||
riskZones={data.nspd_risk_zones ?? []}
|
||
opportunityParcels={data.nspd_opportunity_parcels ?? []}
|
||
redLines={data.nspd_red_lines ?? []}
|
||
mapHeight={MINIMAP_HEIGHT}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|