156 lines
5.2 KiB
TypeScript
156 lines
5.2 KiB
TypeScript
"use client";
|
||
|
||
import { CircleMarker, LayerGroup, Popup } from "react-leaflet";
|
||
import type { CustomPoi } from "@/types/customPoi";
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Props
|
||
// ---------------------------------------------------------------------------
|
||
|
||
interface Props {
|
||
pois: CustomPoi[];
|
||
onEdit: (poi: CustomPoi) => void;
|
||
onDelete: (id: number) => void;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Color helpers
|
||
// ---------------------------------------------------------------------------
|
||
|
||
function markerColor(weight: number): string {
|
||
if (weight > 0) return "#16a34a"; // green — positive
|
||
if (weight < 0) return "#dc2626"; // red — negative
|
||
return "#6b7280"; // gray — neutral
|
||
}
|
||
|
||
function weightLabel(weight: number): string {
|
||
if (weight > 0) return `+${weight}`;
|
||
return String(weight);
|
||
}
|
||
|
||
// Человекочитаемая дата (ru) с guard против null / невалидного ISO.
|
||
// Repo-convention: inline new Date(...).toLocaleDateString("ru-RU") + NaN-fallback
|
||
// (см. EgrnPropertyTable.formatDate).
|
||
function formatDate(raw: string | null | undefined): string | null {
|
||
if (!raw) return null;
|
||
const d = new Date(raw);
|
||
if (Number.isNaN(d.getTime())) return null;
|
||
return d.toLocaleDateString("ru-RU");
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Component
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export function CustomPoiLayer({ pois, onEdit, onDelete }: Props) {
|
||
if (!pois.length) return null;
|
||
|
||
return (
|
||
<LayerGroup>
|
||
{pois.map((poi) => {
|
||
const color = markerColor(poi.weight);
|
||
const isGlobal = !poi.parcel_cad;
|
||
const createdStr = formatDate(poi.created_at);
|
||
const updatedStr = formatDate(poi.updated_at);
|
||
// «изменена» показываем только если updated_at отличается от created_at.
|
||
const wasEdited =
|
||
!!poi.updated_at &&
|
||
!!poi.created_at &&
|
||
poi.updated_at !== poi.created_at;
|
||
return (
|
||
<CircleMarker
|
||
key={poi.id}
|
||
center={[poi.lat, poi.lon]}
|
||
radius={9}
|
||
pathOptions={{
|
||
color,
|
||
fillColor: color,
|
||
fillOpacity: 0.85,
|
||
weight: 2.5,
|
||
dashArray: "4 2",
|
||
}}
|
||
>
|
||
<Popup>
|
||
<div style={{ fontSize: 13, lineHeight: 1.6, minWidth: 160 }}>
|
||
<div
|
||
style={{ fontWeight: 700, marginBottom: 2, color: "#111827" }}
|
||
>
|
||
{poi.name}
|
||
</div>
|
||
{poi.category && (
|
||
<div
|
||
style={{ fontSize: 11, color: "#6b7280", marginBottom: 4 }}
|
||
>
|
||
{poi.category}
|
||
</div>
|
||
)}
|
||
<div
|
||
style={{ fontSize: 11, color: "#6b7280", marginBottom: 4 }}
|
||
>
|
||
{isGlobal
|
||
? "Глобальная точка"
|
||
: `Привязана к участку ${poi.parcel_cad}`}
|
||
</div>
|
||
<div
|
||
style={{
|
||
fontWeight: 700,
|
||
color,
|
||
marginBottom: 6,
|
||
fontSize: 14,
|
||
}}
|
||
>
|
||
Вес: {weightLabel(poi.weight)}
|
||
</div>
|
||
{poi.notes && (
|
||
<div
|
||
style={{ fontSize: 12, color: "#374151", marginBottom: 8 }}
|
||
>
|
||
{poi.notes}
|
||
</div>
|
||
)}
|
||
<div style={{ display: "flex", gap: 8 }}>
|
||
<button
|
||
type="button"
|
||
onClick={() => onEdit(poi)}
|
||
style={{
|
||
padding: "4px 10px",
|
||
fontSize: 12,
|
||
borderRadius: 5,
|
||
border: "1px solid #d1d5db",
|
||
background: "#f9fafb",
|
||
cursor: "pointer",
|
||
color: "#374151",
|
||
}}
|
||
>
|
||
Изменить
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={() => onDelete(poi.id)}
|
||
style={{
|
||
padding: "4px 10px",
|
||
fontSize: 12,
|
||
borderRadius: 5,
|
||
border: "none",
|
||
background: "#fef2f2",
|
||
color: "#dc2626",
|
||
cursor: "pointer",
|
||
}}
|
||
>
|
||
Удалить
|
||
</button>
|
||
</div>
|
||
{createdStr && (
|
||
<div style={{ marginTop: 8, fontSize: 10, color: "#9ca3af" }}>
|
||
Добавлена {createdStr}
|
||
{wasEdited && updatedStr ? ` · изменена ${updatedStr}` : ""}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</Popup>
|
||
</CircleMarker>
|
||
);
|
||
})}
|
||
</LayerGroup>
|
||
);
|
||
}
|