fix(site-finder/v2): remove resource legend overlay (dup of ИНЖЕНЕРИЯ card + POI overlap) #1870

Merged
bot-backend merged 1 commit from fix/v2-remove-resource-legend into main 2026-06-22 06:51:27 +00:00
3 changed files with 1 additions and 205 deletions

View file

@ -1910,64 +1910,6 @@
display: inline; display: inline;
} }
/* Resource legend (bottom-left of the map card) compact 5-row panel.
Placed bottom-left, above Leaflet attribution, to avoid overlap with the
existing PticaMapInner POI/category legend that lives in the top-left. */
.mapLegend {
position: absolute;
left: 11px;
bottom: 28px;
z-index: 6;
min-width: 168px;
max-width: 200px;
background: var(--surface-strong);
border: 1px solid var(--border);
border-radius: var(--radius-xs);
padding: 8px 9px;
backdrop-filter: blur(3px);
pointer-events: none;
}
.mapLegendTitle {
font-family: var(--font-mono);
font-size: 7.5px;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--text-soft);
margin: 0 0 5px;
}
.mapLegendRow {
display: flex;
align-items: center;
gap: 6px;
height: 15px;
font-size: 9px;
}
.mapLegendDot {
width: 8px;
height: 8px;
border-radius: 50%;
flex: 0 0 auto;
box-shadow: 0 0 5px currentColor;
}
.mapLegendLabel {
font-family: var(--font-mono);
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--text);
font-size: 8.5px;
}
.mapLegendDist {
margin-left: auto;
font-family: var(--font-mono);
font-size: 8.5px;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
}
.mapLegendDistEmpty {
color: var(--text-soft);
opacity: 0.7;
}
/* ===================================================================== /* =====================================================================
* LIGHT THEME targeted contrast polish * LIGHT THEME targeted contrast polish
* Tokens stay; just bump the value-side text and the cyan accents that * Tokens stay; just bump the value-side text and the cyan accents that
@ -1990,16 +1932,7 @@
} }
/* Map-legend value column — same readability bump on white. */ /* Map-legend value column — same readability bump on white. */
.v2Root[data-theme="light"] .mapLegendDist { .v2Root[data-theme="light"] .v2Root[data-theme="light"] .v2Root[data-theme="light"] /* Map title (light): drop the dark text-shadow that makes thin uppercase
color: var(--text-strong);
}
.v2Root[data-theme="light"] .mapLegendLabel {
color: var(--text-strong);
}
.v2Root[data-theme="light"] .mapLegendDot {
box-shadow: none;
}
/* Map title (light): drop the dark text-shadow that makes thin uppercase
look smudgy on a satellite-light map base. */ look smudgy on a satellite-light map base. */
.v2Root[data-theme="light"] .mapHead .ttl { .v2Root[data-theme="light"] .mapHead .ttl {
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.6); text-shadow: 0 1px 2px rgba(255, 255, 255, 0.6);

View file

@ -19,7 +19,6 @@ import styles from "@/app/site-finder/analysis/[cad]/v2/v2.module.css";
import type { ParcelAnalysis } from "@/types/site-finder"; import type { ParcelAnalysis } from "@/types/site-finder";
import { useConnectionPoints } from "@/hooks/useConnectionPoints"; import { useConnectionPoints } from "@/hooks/useConnectionPoints";
import { useCustomPois } from "@/hooks/useCustomPois"; import { useCustomPois } from "@/hooks/useCustomPois";
import { V2MapLegend } from "@/components/site-finder/ptica-v2/V2MapLegend";
const PticaMapInner = dynamic( const PticaMapInner = dynamic(
() => import("@/components/site-finder/ptica/PticaMapInner"), () => import("@/components/site-finder/ptica/PticaMapInner"),
@ -61,9 +60,6 @@ export function V2Map({ analysis }: Props) {
СПУТНИК · СХЕМА ПОДКЛЮЧЕНИЙ СПУТНИК · СХЕМА ПОДКЛЮЧЕНИЙ
</span> </span>
</div> </div>
{/* Resource legend overlay (sibling of PticaMapInner, sits on top of the
map via absolute positioning does not touch the shared inner map). */}
<V2MapLegend analysis={analysis} />
</div> </div>
); );
} }

View file

@ -1,133 +0,0 @@
"use client";
/**
* V2MapLegend compact resource legend overlay rendered ON TOP of the cockpit
* map card (prototype `ptica-v2/index.html`, block `.legend > .lg-block.lg-res`).
*
* Five rows ЭЛЕКТРИЧЕСТВО / ВОДА / КАНАЛИЗАЦИЯ / ТЕПЛОСНАБЖЕНИЕ / ГАЗ each
* showing the distance to the nearest engineering node + a status-dot (green when
* the узел is within ~1 km, orange when further). Data comes from
* `analysis.utilities.summary[]` whose `subtype` values are the OSM `road_class`
* tags produced by the backend (see `noise_loader._NOISE_QUERIES` + `parcels.py`
* `_nearest()` aliases) we aggregate them into the 5 prototype buckets.
*
* The legend is a SIBLING of `PticaMapInner` inside `V2Map` (the inner Leaflet
* mount is shared with the старый ptica route and must NOT be touched). All
* styles live in the scoped v2 module so the overlay never leaks into the
* application chrome.
*/
import styles from "@/app/site-finder/analysis/[cad]/v2/v2.module.css";
import type { ParcelAnalysis } from "@/types/site-finder";
type ResourceKey = "electric" | "water" | "sewer" | "heat" | "gas";
interface ResourceDef {
key: ResourceKey;
label: string;
/** OSM road_class values that count toward this bucket. */
subtypes: readonly string[];
/** CSS var for the dot accent color (matches prototype --res-* tokens). */
color: string;
}
// Aliases mirror backend `_nearest()` in `backend/app/api/v1/parcels.py`:
// electric ← substation | transformer | power_line
// water ← water_main | water_works | water_tower
// sewer ← sewerage
// heat ← heat_substation
// gas ← gas_pipeline (legacy generic «pipeline» falls under gas)
const RESOURCES: readonly ResourceDef[] = [
{
key: "electric",
label: "ЭЛЕКТРИЧЕСТВО",
subtypes: ["substation", "transformer", "power_line"],
color: "var(--res-electric)",
},
{
key: "water",
label: "ВОДА",
subtypes: ["water_main", "water_works", "water_tower"],
color: "var(--res-water)",
},
{
key: "sewer",
label: "КАНАЛИЗАЦИЯ",
subtypes: ["sewerage"],
color: "var(--res-sewer)",
},
{
key: "heat",
label: "ТЕПЛОСНАБЖЕНИЕ",
subtypes: ["heat_substation"],
color: "var(--res-heat)",
},
{
key: "gas",
label: "ГАЗ",
subtypes: ["gas_pipeline", "pipeline"],
color: "var(--res-gas)",
},
] as const;
const OK_THRESHOLD_M = 1000;
function formatDistanceMeters(m: number): string {
// Prototype uses tabular-num, single-line «1 240 м». Round to whole meters.
return `${Math.round(m).toLocaleString("ru-RU")} м`;
}
function nearestForResource(
summary: ReadonlyArray<{ subtype: string; nearest_m: number }>,
subtypes: readonly string[],
): number | null {
let best: number | null = null;
for (const u of summary) {
if (!subtypes.includes(u.subtype)) continue;
if (best === null || u.nearest_m < best) best = u.nearest_m;
}
return best;
}
interface Props {
analysis: ParcelAnalysis;
}
export function V2MapLegend({ analysis }: Props) {
const summary = analysis.utilities?.summary ?? [];
return (
<div className={styles.mapLegend} aria-label="Подключение ресурсов">
<div className={styles.mapLegendTitle}>ТОЧКИ ПОДКЛЮЧЕНИЯ РЕСУРСОВ</div>
{RESOURCES.map((res) => {
const nearest = nearestForResource(summary, res.subtypes);
const hasData = nearest !== null;
const ok = hasData && nearest! <= OK_THRESHOLD_M;
const dotColor = hasData
? ok
? "var(--accent-green)"
: "var(--accent-orange)"
: "var(--text-soft)";
return (
<div
key={res.key}
className={styles.mapLegendRow}
style={{ color: res.color }}
>
<span
className={styles.mapLegendDot}
style={{ background: dotColor, color: dotColor }}
aria-hidden="true"
/>
<span className={styles.mapLegendLabel}>{res.label}</span>
<span
className={`${styles.mapLegendDist} ${hasData ? "" : styles.mapLegendDistEmpty}`}
>
{hasData ? formatDistanceMeters(nearest!) : "—"}
</span>
</div>
);
})}
</div>
);
}