fix(site-finder): MiniMap не клипает layer-controls — рыночные слои доступны (#1218)
Section 1 mini-map (/site-finder/analysis/[cad] — единственная карта
страницы) оборачивал SiteMap в height:320 + overflow:hidden. SiteMap
рендерит карту фиксированной высотой 420px + ПОД ней (flow) кнопку
"Добавить POI", легенду и CpLayerControlPanel с тумблерами
competitors/pipeline/risk/opportunity/redlines. Нижние 100px карты и
ВСЕ контролы обрезались clip-обёрткой → недостижимы кликом.
visibleMarketLayers init = {"competitors"} — единственный способ
включить остальные слои (#999 zone-of-risk, #958 §12.1-13 opportunity
parcels / red lines) — те самые скрытые чекбоксы → слои физически
невозможно было включить на странице анализа, хотя данные пробрасывались
props'ами ради них.
Patch (Вариант A из issue):
- SiteMap.tsx: новый prop mapHeight?: number (default 420 — backward-
compat для /site-finder/[cad]).
- MiniMap.tsx: снято height:320 + overflow:hidden; передаём mapHeight=280
(компактнее но не клипает); только width-constraint (maxWidth 400)
на обёртке.
2 новых vitest: mapHeight=280 пробрасывается в SiteMap (mocked stub),
clipping-стили отсутствуют. 82/82 frontend тестов зелёные. tsc + lint clean.
Closes #1218
This commit is contained in:
parent
bc2d393b05
commit
0a8785d548
3 changed files with 100 additions and 18 deletions
|
|
@ -126,6 +126,11 @@ interface Props {
|
|||
// §12.1-13 (#958) — opportunity-ЗУ + красные линии застройки (geom_wkt).
|
||||
opportunityParcels?: OpportunityParcel[];
|
||||
redLines?: RedLine[];
|
||||
// #1218 — высота тайла карты (px). Default 420 — режим обычной странички
|
||||
// /site-finder/[cad]. MiniMap на /analysis передаёт меньше (≈280), чтобы
|
||||
// карта была компактнее. На высоту НЕ влияет на flow-контролы ниже
|
||||
// (POI add button, легенда, CpLayerControlPanel) — они идут потоком.
|
||||
mapHeight?: number;
|
||||
}
|
||||
|
||||
// Ключи переключаемых рыночных слоёв (#999 + §12.1-13 #958).
|
||||
|
|
@ -167,6 +172,7 @@ export function SiteMap({
|
|||
riskZones,
|
||||
opportunityParcels,
|
||||
redLines,
|
||||
mapHeight = 420,
|
||||
}: Props) {
|
||||
// Fix Leaflet default icon paths broken by webpack bundler
|
||||
useEffect(() => {
|
||||
|
|
@ -310,7 +316,7 @@ export function SiteMap({
|
|||
border: "1px solid #e5e7eb",
|
||||
borderRadius: 12,
|
||||
overflow: "hidden",
|
||||
height: 420,
|
||||
height: mapHeight,
|
||||
cursor: addMode ? "crosshair" : "grab",
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
"use client";
|
||||
|
||||
/**
|
||||
* MiniMap — compact Leaflet map (400×320) showing parcel polygon + top POI.
|
||||
* 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 fixed dimensions for Section 1 layout.
|
||||
* 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";
|
||||
|
|
@ -11,6 +17,10 @@ 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(
|
||||
() =>
|
||||
|
|
@ -22,7 +32,7 @@ const SiteMap = dynamic(
|
|||
loading: () => (
|
||||
<div
|
||||
style={{
|
||||
height: 320,
|
||||
height: MINIMAP_HEIGHT,
|
||||
background: "var(--bg-card-alt)",
|
||||
borderRadius: 12,
|
||||
border: "1px solid var(--border-card)",
|
||||
|
|
@ -75,28 +85,27 @@ function toSiteMapData(data: ParcelAnalyzeResponse): ParcelAnalysis {
|
|||
export function MiniMap({ data }: Props) {
|
||||
const adapted = toSiteMapData(data);
|
||||
|
||||
// #1218: НЕ задаём height + overflow:hidden на обёртке. SiteMap сам
|
||||
// ограничивает высоту карты-тайла (mapHeight), а легенда + layer-controls
|
||||
// (Будущие проекты / Зоны риска / Перспективные ЗУ / Красные линии) лежат
|
||||
// под картой потоком и должны быть видны/кликабельны.
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
maxWidth: 400,
|
||||
height: 320,
|
||||
borderRadius: 12,
|
||||
overflow: "hidden",
|
||||
border: "1px solid var(--border-card)",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<div style={{ height: 320 }}>
|
||||
<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 ?? []}
|
||||
/>
|
||||
</div>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { MiniMap } from "../MiniMap";
|
||||
import type { ParcelAnalyzeResponse } from "@/lib/site-finder-api";
|
||||
|
||||
// Stub SiteMap so we can assert the props MiniMap passes through (mapHeight
|
||||
// in particular — #1218: ранее MiniMap клипал layer-панель/легенду через
|
||||
// `overflow: hidden + height: 320`; теперь высота карты задаётся через prop).
|
||||
// Stub does NOT use `unknown` — we capture a typed snapshot of the relevant
|
||||
// props (mapHeight) for the assertion.
|
||||
|
||||
interface CapturedProps {
|
||||
mapHeight?: number;
|
||||
}
|
||||
|
||||
const captured: { last: CapturedProps | null } = { last: null };
|
||||
|
||||
vi.mock("@/components/site-finder/SiteMap", () => ({
|
||||
SiteMap: (props: CapturedProps) => {
|
||||
captured.last = { mapHeight: props.mapHeight };
|
||||
return <div data-testid="sitemap-stub">map(h={props.mapHeight ?? "?"})</div>;
|
||||
},
|
||||
}));
|
||||
|
||||
// Minimal ParcelAnalyzeResponse — only fields MiniMap touches (toSiteMapData).
|
||||
const FIXTURE: ParcelAnalyzeResponse = {
|
||||
cad_num: "66:41:0000000:1",
|
||||
source: "cad_building",
|
||||
geom_geojson: null,
|
||||
district: null,
|
||||
score: 0,
|
||||
score_breakdown: {},
|
||||
poi_count: 0,
|
||||
competitors: [],
|
||||
pipeline_24mo: null,
|
||||
nspd_risk_zones: [],
|
||||
nspd_opportunity_parcels: [],
|
||||
nspd_red_lines: [],
|
||||
};
|
||||
|
||||
describe("MiniMap (#1218)", () => {
|
||||
it("passes mapHeight=280 to SiteMap (compact tile, controls flow below)", async () => {
|
||||
render(<MiniMap data={FIXTURE} />);
|
||||
// SiteMap is dynamically imported (ssr:false) — wait until our stub renders.
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("sitemap-stub")).toBeInTheDocument(),
|
||||
);
|
||||
expect(captured.last?.mapHeight).toBe(280);
|
||||
});
|
||||
|
||||
it("wrapper does NOT clip overflow — layer controls must remain visible", async () => {
|
||||
const { container } = render(<MiniMap data={FIXTURE} />);
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("sitemap-stub")).toBeInTheDocument(),
|
||||
);
|
||||
// The outer wrapper is the first element child — SiteMap stub lives inside.
|
||||
const wrapper = container.firstElementChild as HTMLElement | null;
|
||||
expect(wrapper).not.toBeNull();
|
||||
// No fixed height (must be empty so flow children — legend + control panel —
|
||||
// are not clipped to 320px), and no overflow: hidden.
|
||||
expect(wrapper?.style.height).toBe("");
|
||||
expect(wrapper?.style.overflow).toBe("");
|
||||
// Width constraint preserved (column slot is 400px in Section 1 grid).
|
||||
expect(wrapper?.style.maxWidth).toBe("400px");
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue