ПТИЦА cockpit: - map: POI/competitors/connections now render as glyph divIcon pins (per-category glyph + color) with dashed connection lines + a compass — matches the prototype (was plain CircleMarker dots). Legend/zoom/scale/base-toggle/tools kept. - lower+bottom grids: align-items stretch → equal-height rows; cards are flex columns with footer «Подробнее» pinned to bottom and sparse empty-states centred (fixes «выровняй тут все»). - rail: dropped sticky+height:100vh so it stretches to full page height like the prototype (fixes «обрезан»). Main Site Finder (entry landing) — restyled to the ПТИЦА dark cockpit theme, scoped to .sfRoot[data-theme=dark] (no leak to light pages): dark CARTO tiles, Leaflet attribution flag removed (prefix=false, © credit kept), dark markers / filter chips / legend / recent list / drawer / cad input.
376 lines
9.7 KiB
TypeScript
376 lines
9.7 KiB
TypeScript
"use client";
|
||
|
||
import Link from "next/link";
|
||
import { X, MapPin, Maximize2, ArrowRight } from "lucide-react";
|
||
import type { ParcelBboxItem } from "@/lib/site-finder-api";
|
||
import { addLocalRecentParcel } from "@/lib/site-finder-api";
|
||
import { STATUS_COLORS, STATUS_LABELS } from "./ParcelLegend";
|
||
|
||
interface ParcelDrawerProps {
|
||
parcel: ParcelBboxItem | null;
|
||
onClose: () => void;
|
||
}
|
||
|
||
const VRI_LABELS: Record<string, string> = {
|
||
multistory: "Многоэтажный",
|
||
mixed: "Смешанный",
|
||
individual: "ИЖС",
|
||
office: "Офисный",
|
||
other: "Иное",
|
||
};
|
||
|
||
export function ParcelDrawer({ parcel, onClose }: ParcelDrawerProps) {
|
||
if (!parcel) return null;
|
||
|
||
const statusColor = STATUS_COLORS[parcel.status] ?? "#8ba6b3";
|
||
const statusLabel = STATUS_LABELS[parcel.status] ?? parcel.status;
|
||
|
||
return (
|
||
// Drawer panel — absolute within the map container (parent has position:relative).
|
||
// This keeps the sidebar (CadInput/RecentParcels/ParcelLegend) always visible
|
||
// and only overlaps the map area on parcel select.
|
||
<aside
|
||
style={{
|
||
position: "absolute",
|
||
top: 16,
|
||
right: 16,
|
||
bottom: 16,
|
||
width: 360,
|
||
zIndex: 20,
|
||
background: "rgba(12, 30, 42, 0.94)",
|
||
border: "1px solid rgba(150, 192, 214, 0.18)",
|
||
borderRadius: 12,
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
boxShadow: "0 18px 70px rgba(0, 0, 0, 0.5)",
|
||
backdropFilter: "blur(6px)",
|
||
}}
|
||
role="dialog"
|
||
aria-label="Карточка участка"
|
||
>
|
||
{/* Header */}
|
||
<div
|
||
style={{
|
||
padding: "16px 20px 12px",
|
||
borderBottom: "1px solid rgba(150, 192, 214, 0.1)",
|
||
display: "flex",
|
||
alignItems: "flex-start",
|
||
gap: 8,
|
||
}}
|
||
>
|
||
<MapPin
|
||
size={16}
|
||
strokeWidth={1.5}
|
||
style={{
|
||
color: "#8ba6b3",
|
||
marginTop: 2,
|
||
flexShrink: 0,
|
||
}}
|
||
/>
|
||
<div style={{ flex: 1, minWidth: 0 }}>
|
||
<div
|
||
style={{
|
||
fontSize: 11,
|
||
fontWeight: 500,
|
||
textTransform: "uppercase",
|
||
letterSpacing: "0.04em",
|
||
color: "#5f7886",
|
||
fontVariantNumeric: "tabular-nums",
|
||
}}
|
||
>
|
||
{parcel.cad_num}
|
||
</div>
|
||
<div
|
||
style={{
|
||
fontSize: 13,
|
||
color: "#8ba6b3",
|
||
marginTop: 2,
|
||
overflow: "hidden",
|
||
textOverflow: "ellipsis",
|
||
whiteSpace: "nowrap",
|
||
}}
|
||
>
|
||
{parcel.address}
|
||
</div>
|
||
</div>
|
||
<button
|
||
onClick={onClose}
|
||
aria-label="Закрыть"
|
||
style={{
|
||
background: "none",
|
||
border: "none",
|
||
cursor: "pointer",
|
||
padding: 4,
|
||
color: "#8ba6b3",
|
||
flexShrink: 0,
|
||
}}
|
||
>
|
||
<X size={18} strokeWidth={1.5} />
|
||
</button>
|
||
</div>
|
||
|
||
{/* KPI grid */}
|
||
<div
|
||
style={{
|
||
padding: "16px 20px",
|
||
display: "grid",
|
||
gridTemplateColumns: "1fr 1fr",
|
||
gap: 12,
|
||
}}
|
||
>
|
||
{/* Status */}
|
||
<div
|
||
style={{
|
||
background: "rgba(4, 12, 18, 0.6)",
|
||
border: "1px solid rgba(150, 192, 214, 0.18)",
|
||
borderRadius: 8,
|
||
padding: "10px 12px",
|
||
}}
|
||
>
|
||
<div
|
||
style={{
|
||
fontSize: 11,
|
||
fontWeight: 500,
|
||
textTransform: "uppercase",
|
||
letterSpacing: "0.04em",
|
||
color: "#8ba6b3",
|
||
marginBottom: 4,
|
||
}}
|
||
>
|
||
Статус
|
||
</div>
|
||
<div
|
||
style={{
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 6,
|
||
}}
|
||
>
|
||
<span
|
||
style={{
|
||
width: 8,
|
||
height: 8,
|
||
borderRadius: "50%",
|
||
background: statusColor,
|
||
flexShrink: 0,
|
||
}}
|
||
/>
|
||
<span
|
||
style={{
|
||
fontSize: 13,
|
||
fontWeight: 500,
|
||
color: "#dcebf2",
|
||
}}
|
||
>
|
||
{statusLabel}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Area */}
|
||
<div
|
||
style={{
|
||
background: "rgba(4, 12, 18, 0.6)",
|
||
border: "1px solid rgba(150, 192, 214, 0.18)",
|
||
borderRadius: 8,
|
||
padding: "10px 12px",
|
||
}}
|
||
>
|
||
<div
|
||
style={{
|
||
fontSize: 11,
|
||
fontWeight: 500,
|
||
textTransform: "uppercase",
|
||
letterSpacing: "0.04em",
|
||
color: "#8ba6b3",
|
||
marginBottom: 4,
|
||
}}
|
||
>
|
||
Площадь
|
||
</div>
|
||
<div
|
||
style={{
|
||
fontSize: 18,
|
||
fontWeight: 600,
|
||
color: "#dcebf2",
|
||
fontVariantNumeric: "tabular-nums",
|
||
}}
|
||
>
|
||
{parcel.area_ha != null ? parcel.area_ha.toFixed(2) : "—"}{" "}
|
||
<span
|
||
style={{
|
||
fontSize: 13,
|
||
fontWeight: 400,
|
||
color: "#8ba6b3",
|
||
}}
|
||
>
|
||
га
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* District */}
|
||
<div
|
||
style={{
|
||
background: "rgba(4, 12, 18, 0.6)",
|
||
border: "1px solid rgba(150, 192, 214, 0.18)",
|
||
borderRadius: 8,
|
||
padding: "10px 12px",
|
||
}}
|
||
>
|
||
<div
|
||
style={{
|
||
fontSize: 11,
|
||
fontWeight: 500,
|
||
textTransform: "uppercase",
|
||
letterSpacing: "0.04em",
|
||
color: "#8ba6b3",
|
||
marginBottom: 4,
|
||
}}
|
||
>
|
||
Район
|
||
</div>
|
||
<div
|
||
style={{
|
||
fontSize: 13,
|
||
fontWeight: 500,
|
||
color: "#dcebf2",
|
||
}}
|
||
>
|
||
{parcel.district}
|
||
</div>
|
||
</div>
|
||
|
||
{/* VRI */}
|
||
<div
|
||
style={{
|
||
background: "rgba(4, 12, 18, 0.6)",
|
||
border: "1px solid rgba(150, 192, 214, 0.18)",
|
||
borderRadius: 8,
|
||
padding: "10px 12px",
|
||
}}
|
||
>
|
||
<div
|
||
style={{
|
||
fontSize: 11,
|
||
fontWeight: 500,
|
||
textTransform: "uppercase",
|
||
letterSpacing: "0.04em",
|
||
color: "#8ba6b3",
|
||
marginBottom: 4,
|
||
}}
|
||
>
|
||
ВРИ
|
||
</div>
|
||
<div
|
||
style={{
|
||
fontSize: 13,
|
||
fontWeight: 500,
|
||
color: "#dcebf2",
|
||
}}
|
||
>
|
||
{VRI_LABELS[parcel.vri] ?? parcel.vri}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Coords */}
|
||
<div
|
||
style={{
|
||
background: "rgba(4, 12, 18, 0.6)",
|
||
border: "1px solid rgba(150, 192, 214, 0.18)",
|
||
borderRadius: 8,
|
||
padding: "10px 12px",
|
||
gridColumn: "1 / -1",
|
||
}}
|
||
>
|
||
<div
|
||
style={{
|
||
fontSize: 11,
|
||
fontWeight: 500,
|
||
textTransform: "uppercase",
|
||
letterSpacing: "0.04em",
|
||
color: "#8ba6b3",
|
||
marginBottom: 4,
|
||
}}
|
||
>
|
||
Координаты
|
||
</div>
|
||
<div
|
||
style={{
|
||
fontSize: 12,
|
||
color: "#8ba6b3",
|
||
fontVariantNumeric: "tabular-nums",
|
||
}}
|
||
>
|
||
{parcel.lat.toFixed(5)}, {parcel.lon.toFixed(5)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Spacer */}
|
||
<div style={{ flex: 1 }} />
|
||
|
||
{/* CTA */}
|
||
<div
|
||
style={{
|
||
padding: "16px 20px",
|
||
borderTop: "1px solid rgba(150, 192, 214, 0.1)",
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
gap: 8,
|
||
}}
|
||
>
|
||
<Link
|
||
href={`/site-finder/analysis/${encodeURIComponent(parcel.cad_num)}`}
|
||
onClick={() => {
|
||
// Record the visit so this entry path also lands in RecentParcels (#1477).
|
||
addLocalRecentParcel({
|
||
cad_num: parcel.cad_num,
|
||
address: parcel.address,
|
||
area_ha: parcel.area_ha,
|
||
district: parcel.district,
|
||
visited_at: new Date().toISOString(),
|
||
});
|
||
}}
|
||
style={{
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
gap: 8,
|
||
background: "rgba(127, 208, 238, 0.16)",
|
||
color: "#8ed3ff",
|
||
border: "1px solid rgba(127, 208, 238, 0.55)",
|
||
borderRadius: 8,
|
||
padding: "10px 20px",
|
||
fontSize: 14,
|
||
fontWeight: 500,
|
||
textDecoration: "none",
|
||
}}
|
||
>
|
||
Открыть анализ
|
||
<ArrowRight size={16} strokeWidth={1.5} />
|
||
</Link>
|
||
<a
|
||
href={`https://pkk.rosreestr.ru/#/search/cadastralNumber/${encodeURIComponent(parcel.cad_num)}`}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
style={{
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
gap: 8,
|
||
background: "none",
|
||
color: "#8ba6b3",
|
||
borderRadius: 8,
|
||
padding: "8px 20px",
|
||
fontSize: 13,
|
||
textDecoration: "none",
|
||
}}
|
||
>
|
||
<Maximize2 size={14} strokeWidth={1.5} />
|
||
ПКК Росреестр
|
||
</a>
|
||
</div>
|
||
</aside>
|
||
);
|
||
}
|