gendesign/frontend/src/components/site-finder/ZouitLayerControlPanel.tsx
Light1YT 0b20d98b45
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 2m49s
Deploy / deploy (push) Successful in 1m6s
feat(site-finder): ЗОУИТ visual layer на Leaflet карте (#255)
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
2026-06-13 17:41:08 +00:00

130 lines
4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
/**
* ZouitLayerControlPanel — #255: тумблеры ЗОУИТ-слоя (по образцу
* CpLayerControlPanel). Collapsible, checkbox per-severity с цветным dot +
* счётчиком рисуемых полигонов.
*
* Счётчики берём из grouped (groupZouitBySeverity) — это РИСУЕМЫЕ объекты
* (валидная полигональная геометрия), а не все overlaps: ЗОУИТ без geom_geojson
* на карте не появится, поэтому панель не должна обещать его пользователю.
*/
import { useState } from "react";
import {
ZOUIT_ALL_SEVERITIES,
ZOUIT_SEVERITY_STYLES,
type ZouitMappable,
type ZouitSeverity,
} from "@/components/site-finder/ZouitLayer";
interface Props {
grouped: Map<ZouitSeverity, ZouitMappable[]>;
visibleSeverities: Set<ZouitSeverity>;
onToggleSeverity: (sev: ZouitSeverity) => void;
}
export function ZouitLayerControlPanel({
grouped,
visibleSeverities,
onToggleSeverity,
}: Props) {
const [collapsed, setCollapsed] = useState(false);
const totalCount = ZOUIT_ALL_SEVERITIES.reduce(
(sum, sev) => sum + (grouped.get(sev)?.length ?? 0),
0,
);
return (
<div
style={{
background: "#fff",
border: "1px solid #e5e7eb",
borderRadius: 10,
marginTop: 10,
fontSize: 12,
}}
>
{/* Header */}
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "8px 12px",
cursor: "pointer",
userSelect: "none",
borderBottom: collapsed ? "none" : "1px solid #f3f4f6",
}}
onClick={() => setCollapsed((v) => !v)}
>
<span style={{ fontWeight: 700, color: "#1f2937" }}>
Охранные зоны ЗОУИТ
</span>
<span style={{ color: "#6b7280", fontSize: 11 }}>
{totalCount} зон {collapsed ? "▲" : "▼"}
</span>
</div>
{!collapsed && (
<div style={{ padding: "8px 12px 10px" }}>
{totalCount === 0 ? (
<div style={{ color: "#6b7280", fontSize: 11 }}>
Нет геометрии охранных зон для отображения
</div>
) : (
<div
style={{
display: "flex",
flexWrap: "wrap",
gap: "4px 14px",
}}
>
{ZOUIT_ALL_SEVERITIES.map((sev) => {
const items = grouped.get(sev) ?? [];
if (items.length === 0) return null;
const style = ZOUIT_SEVERITY_STYLES[sev];
const active = visibleSeverities.has(sev);
return (
<label
key={sev}
style={{
display: "flex",
alignItems: "center",
gap: 5,
cursor: "pointer",
color: "#374151",
opacity: active ? 1 : 0.45,
transition: "opacity 0.15s",
}}
>
<input
type="checkbox"
checked={active}
onChange={() => onToggleSeverity(sev)}
style={{ cursor: "pointer" }}
/>
<span
style={{
display: "inline-block",
width: 10,
height: 10,
borderRadius: 2,
background: style.color,
flexShrink: 0,
}}
/>
{style.label}
<span style={{ color: "#9ca3af" }}>{items.length}</span>
</label>
);
})}
</div>
)}
</div>
)}
</div>
);
}