gendesign/frontend/src/components/site-finder/UtilityLayerControlPanel.tsx
Light1YT 854fa69a8b
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 54s
CI / openapi-codegen-check (pull_request) Successful in 1m49s
feat(site-finder): render OSM utility-infrastructure layer on analyze map (#1746)
Section2 "Сети" map now shows the OSM engineering-networks layer
(osm_utility_infrastructure_ekb, endpoint /parcels/{cad}/utility-infrastructure)
alongside the NSPD connection-points + ЗОУИТ layers from #1751, on the same map.

- useUtilityInfrastructure hook (react-query, mirrors useConnectionPoints)
- UtilityInfrastructureLayer: Point→CircleMarker, LineString/Polygon(+Multi)→GeoJSON,
  per-kind colour (power/water/gas/heat/communication/sewage), popups
- UtilityLayerControlPanel: collapsible legend, per-kind toggle + count
- SiteMap: optional utilityInfrastructure prop + visibleKinds state (CP/ЗОУИТ unchanged)
- 12 unit tests (geometry parsing + [lon,lat]→[lat,lon] swap + grouping)

Refs #1746
2026-06-27 03:40:51 +05:00

164 lines
5.2 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";
/**
* UtilityLayerControlPanel — #1746: тумблеры слоя инженерной инфраструктуры OSM
* (по образцу CpLayerControlPanel / ZouitLayerControlPanel). Collapsible,
* checkbox per-kind с цветным dot + счётчиком рисуемых объектов, «показать все».
*
* Счётчики берём из grouped (groupUtilityByKind) — это РИСУЕМЫЕ объекты (валидная
* геометрия), а не все features: фича без геометрии на карте не появится, поэтому
* панель не должна обещать её пользователю.
*/
import { useState } from "react";
import {
UTILITY_ALL_KINDS,
UTILITY_KIND_STYLES,
type UtilityKind,
type UtilityMappable,
} from "@/components/site-finder/UtilityInfrastructureLayer";
interface Props {
grouped: Map<UtilityKind, UtilityMappable[]>;
visibleKinds: Set<UtilityKind>;
onToggleKind: (kind: UtilityKind) => void;
onToggleAll: () => void;
}
export function UtilityLayerControlPanel({
grouped,
visibleKinds,
onToggleKind,
onToggleAll,
}: Props) {
const [collapsed, setCollapsed] = useState(false);
const totalCount = UTILITY_ALL_KINDS.reduce(
(sum, kind) => sum + (grouped.get(kind)?.length ?? 0),
0,
);
// «Показать все» отражает состояние только непустых (рисуемых) видов.
const togglableKinds = UTILITY_ALL_KINDS.filter(
(kind) => (grouped.get(kind)?.length ?? 0) > 0,
);
const allVisible =
togglableKinds.length > 0 &&
togglableKinds.every((kind) => visibleKinds.has(kind));
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" }}>
Инженерные сети (OSM)
</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 }}>
Данные OSM по инж. сетям не получены
</div>
) : (
<>
{/* Toggle-all */}
<label
style={{
display: "flex",
alignItems: "center",
gap: 6,
marginBottom: 6,
cursor: "pointer",
fontWeight: 600,
color: "#374151",
}}
>
<input
type="checkbox"
checked={allVisible}
onChange={onToggleAll}
style={{ cursor: "pointer" }}
/>
Показать все ({totalCount})
</label>
{/* Per-kind */}
<div
style={{
display: "flex",
flexWrap: "wrap",
gap: "4px 14px",
}}
>
{UTILITY_ALL_KINDS.map((kind) => {
const items = grouped.get(kind) ?? [];
if (items.length === 0) return null;
const style = UTILITY_KIND_STYLES[kind];
const active = visibleKinds.has(kind);
return (
<label
key={kind}
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={() => onToggleKind(kind)}
style={{ cursor: "pointer" }}
/>
<span
style={{
display: "inline-block",
width: 10,
height: 10,
borderRadius: "50%",
background: style.color,
flexShrink: 0,
}}
/>
{style.label}
<span style={{ color: "#9ca3af" }}>{items.length}</span>
</label>
);
})}
</div>
</>
)}
</div>
)}
</div>
);
}