zenit-sdk 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-R5WJ7K2D.mjs → chunk-J2YWF2TS.mjs} +548 -168
- package/dist/chunk-J2YWF2TS.mjs.map +1 -0
- package/dist/index.js +521 -141
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/react/index.js +521 -141
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +1 -1
- package/package.json +1 -1
- package/dist/chunk-R5WJ7K2D.mjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1595,8 +1595,122 @@ function getEffectiveLayerOpacity(baseOpacity, zoom, layerType, geometryType, op
|
|
|
1595
1595
|
var import_react = require("react");
|
|
1596
1596
|
var import_react_leaflet = require("react-leaflet");
|
|
1597
1597
|
var import_leaflet = __toESM(require("leaflet"));
|
|
1598
|
+
|
|
1599
|
+
// src/react/map/geojson-sanitize.ts
|
|
1600
|
+
var warnedKeys = /* @__PURE__ */ new Set();
|
|
1601
|
+
function isFiniteNumber(value) {
|
|
1602
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
1603
|
+
if (typeof value === "string") return Number.isFinite(Number(value));
|
|
1604
|
+
return false;
|
|
1605
|
+
}
|
|
1606
|
+
function isValidLonLat(lon, lat) {
|
|
1607
|
+
const lonN = typeof lon === "string" ? Number(lon) : lon;
|
|
1608
|
+
const latN = typeof lat === "string" ? Number(lat) : lat;
|
|
1609
|
+
if (!isFiniteNumber(lonN) || !isFiniteNumber(latN)) return false;
|
|
1610
|
+
if (typeof lonN !== "number" || typeof latN !== "number") return false;
|
|
1611
|
+
return lonN >= -180 && lonN <= 180 && latN >= -90 && latN <= 90;
|
|
1612
|
+
}
|
|
1613
|
+
function asFeatureCollection(input) {
|
|
1614
|
+
const raw = input && typeof input === "object" && "data" in input ? input.data : input;
|
|
1615
|
+
if (!raw || typeof raw !== "object") {
|
|
1616
|
+
return { type: "FeatureCollection", features: [] };
|
|
1617
|
+
}
|
|
1618
|
+
if (raw.type === "FeatureCollection") {
|
|
1619
|
+
const collection = raw;
|
|
1620
|
+
const features = Array.isArray(collection.features) ? collection.features : [];
|
|
1621
|
+
return { ...collection, features };
|
|
1622
|
+
}
|
|
1623
|
+
if (raw.type === "Feature") {
|
|
1624
|
+
return {
|
|
1625
|
+
type: "FeatureCollection",
|
|
1626
|
+
features: [raw]
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
return { type: "FeatureCollection", features: [] };
|
|
1630
|
+
}
|
|
1631
|
+
function sanitizeFeature(feature) {
|
|
1632
|
+
if (!feature || typeof feature !== "object") return null;
|
|
1633
|
+
const geometry = feature.geometry;
|
|
1634
|
+
if (!geometry || typeof geometry !== "object") return null;
|
|
1635
|
+
const geometryType = geometry.type;
|
|
1636
|
+
const coordinates = geometry.coordinates;
|
|
1637
|
+
if (geometryType === "Point") {
|
|
1638
|
+
if (!Array.isArray(coordinates) || coordinates.length < 2) return null;
|
|
1639
|
+
return isValidLonLat(coordinates[0], coordinates[1]) ? feature : null;
|
|
1640
|
+
}
|
|
1641
|
+
if (geometryType === "MultiPoint") {
|
|
1642
|
+
if (!Array.isArray(coordinates)) return null;
|
|
1643
|
+
const validCoordinates = coordinates.filter((coordinate) => {
|
|
1644
|
+
if (!Array.isArray(coordinate) || coordinate.length < 2) return false;
|
|
1645
|
+
return isValidLonLat(coordinate[0], coordinate[1]);
|
|
1646
|
+
});
|
|
1647
|
+
if (validCoordinates.length === 0) return null;
|
|
1648
|
+
if (validCoordinates.length === coordinates.length) {
|
|
1649
|
+
return feature;
|
|
1650
|
+
}
|
|
1651
|
+
return {
|
|
1652
|
+
...feature,
|
|
1653
|
+
geometry: {
|
|
1654
|
+
...geometry,
|
|
1655
|
+
coordinates: validCoordinates
|
|
1656
|
+
}
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
if (geometryType === "LineString" || geometryType === "Polygon" || geometryType === "MultiPolygon" || geometryType === "MultiLineString") {
|
|
1660
|
+
return Array.isArray(coordinates) && coordinates.length > 0 ? feature : null;
|
|
1661
|
+
}
|
|
1662
|
+
return feature;
|
|
1663
|
+
}
|
|
1664
|
+
function logSanitizeStats(stats, debugKey) {
|
|
1665
|
+
if (process.env.NODE_ENV === "production") return;
|
|
1666
|
+
if (stats.droppedFeatures === 0 && stats.droppedPointsFromMultiPoint === 0) return;
|
|
1667
|
+
const resolvedKey = debugKey ?? "__default__";
|
|
1668
|
+
if (warnedKeys.has(resolvedKey)) return;
|
|
1669
|
+
warnedKeys.add(resolvedKey);
|
|
1670
|
+
console.warn("[zenit-sdk] GeoJSON sanitization dropped invalid geometries.", {
|
|
1671
|
+
layerId: debugKey,
|
|
1672
|
+
...stats
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
function sanitizeGeoJson(input, debugKey) {
|
|
1676
|
+
const featureCollection = asFeatureCollection(input);
|
|
1677
|
+
const safeFeatures = [];
|
|
1678
|
+
const stats = {
|
|
1679
|
+
droppedFeatures: 0,
|
|
1680
|
+
droppedPointsFromMultiPoint: 0
|
|
1681
|
+
};
|
|
1682
|
+
const rawFeatures = Array.isArray(featureCollection.features) ? featureCollection.features : [];
|
|
1683
|
+
rawFeatures.forEach((rawFeature) => {
|
|
1684
|
+
const feature = rawFeature;
|
|
1685
|
+
const geometryType = feature?.geometry?.type;
|
|
1686
|
+
const originalCoords = feature?.geometry?.coordinates;
|
|
1687
|
+
const sanitized = sanitizeFeature(feature);
|
|
1688
|
+
if (!sanitized) {
|
|
1689
|
+
stats.droppedFeatures += 1;
|
|
1690
|
+
if (geometryType === "MultiPoint" && Array.isArray(originalCoords)) {
|
|
1691
|
+
stats.droppedPointsFromMultiPoint += originalCoords.length;
|
|
1692
|
+
}
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
if (geometryType === "MultiPoint" && Array.isArray(originalCoords)) {
|
|
1696
|
+
const safeCoords = sanitized.geometry?.coordinates;
|
|
1697
|
+
const safeCount = Array.isArray(safeCoords) ? safeCoords.length : 0;
|
|
1698
|
+
stats.droppedPointsFromMultiPoint += Math.max(0, originalCoords.length - safeCount);
|
|
1699
|
+
}
|
|
1700
|
+
safeFeatures.push(sanitized);
|
|
1701
|
+
});
|
|
1702
|
+
logSanitizeStats(stats, debugKey);
|
|
1703
|
+
return {
|
|
1704
|
+
...featureCollection,
|
|
1705
|
+
type: "FeatureCollection",
|
|
1706
|
+
features: safeFeatures
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
// src/react/map/layer-geojson.tsx
|
|
1598
1711
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1599
1712
|
var POINT_GEOMETRY_TYPES = /* @__PURE__ */ new Set(["Point", "MultiPoint"]);
|
|
1713
|
+
var DEV_MODE = typeof process !== "undefined" && process.env.NODE_ENV !== "production";
|
|
1600
1714
|
function normalizeBboxFromData(data) {
|
|
1601
1715
|
const bboxCandidate = data.bbox;
|
|
1602
1716
|
if (!Array.isArray(bboxCandidate) || bboxCandidate.length < 4) return null;
|
|
@@ -1629,6 +1743,45 @@ function isNonPointGeometry(feature) {
|
|
|
1629
1743
|
const geometryType = getGeometryType(feature);
|
|
1630
1744
|
return geometryType !== null && !POINT_GEOMETRY_TYPES.has(geometryType);
|
|
1631
1745
|
}
|
|
1746
|
+
function isValidLatLng(latlng) {
|
|
1747
|
+
const candidate = latlng;
|
|
1748
|
+
return Boolean(
|
|
1749
|
+
candidate && Number.isFinite(candidate.lat) && Number.isFinite(candidate.lng)
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
function createInvisibleFallbackMarker(latlng) {
|
|
1753
|
+
return import_leaflet.default.circleMarker(latlng ?? [0, 0], {
|
|
1754
|
+
radius: 0,
|
|
1755
|
+
opacity: 0,
|
|
1756
|
+
fillOpacity: 0,
|
|
1757
|
+
interactive: false
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
function createInvisibleFallbackClusterMarker(latlng) {
|
|
1761
|
+
return import_leaflet.default.marker(latlng ?? [0, 0], {
|
|
1762
|
+
opacity: 0,
|
|
1763
|
+
interactive: false
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
function ensurePaneName(mapInstance, paneName, fallbackPaneName) {
|
|
1767
|
+
if (!mapInstance) return fallbackPaneName;
|
|
1768
|
+
const existingPane = mapInstance.getPane(paneName) ?? mapInstance.createPane(paneName);
|
|
1769
|
+
if (!existingPane && DEV_MODE) {
|
|
1770
|
+
console.warn("[LayerGeoJson] pane unavailable, using fallback", { paneName, fallbackPaneName });
|
|
1771
|
+
}
|
|
1772
|
+
return mapInstance.getPane(paneName) ? paneName : fallbackPaneName;
|
|
1773
|
+
}
|
|
1774
|
+
function createPointDivIcon(style, isMobile) {
|
|
1775
|
+
const size = isMobile ? 18 : 14;
|
|
1776
|
+
const backgroundColor = style.fillColor ?? style.color ?? "#3388ff";
|
|
1777
|
+
const borderColor = style.color ?? style.fillColor ?? "#3388ff";
|
|
1778
|
+
return import_leaflet.default.divIcon({
|
|
1779
|
+
className: "zenit-point-marker",
|
|
1780
|
+
html: `<div style="width:${size}px;height:${size}px;border-radius:50%;background:${backgroundColor};border:2px solid ${borderColor};"></div>`,
|
|
1781
|
+
iconSize: [size, size],
|
|
1782
|
+
iconAnchor: [size / 2, size / 2]
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1632
1785
|
function buildFeatureCollection(features) {
|
|
1633
1786
|
return {
|
|
1634
1787
|
type: "FeatureCollection",
|
|
@@ -1649,14 +1802,15 @@ var LayerGeoJson = ({
|
|
|
1649
1802
|
onEachFeature,
|
|
1650
1803
|
onPolygonLabel
|
|
1651
1804
|
}) => {
|
|
1652
|
-
const
|
|
1653
|
-
const
|
|
1654
|
-
const
|
|
1805
|
+
const safeData = (0, import_react.useMemo)(() => sanitizeGeoJson(data, String(layerId)), [data, layerId]);
|
|
1806
|
+
const features = (0, import_react.useMemo)(() => safeData.features ?? [], [safeData]);
|
|
1807
|
+
const fillFeatures = (0, import_react.useMemo)(() => features.filter(isNonPointGeometry), [features]);
|
|
1808
|
+
const pointFeatures = (0, import_react.useMemo)(() => features.filter(isPointGeometry), [features]);
|
|
1655
1809
|
const dataVersionRef = (0, import_react.useRef)(0);
|
|
1656
1810
|
const prevSignatureRef = (0, import_react.useRef)("");
|
|
1657
1811
|
const firstId = features.length > 0 ? String(features[0]?.id ?? "") : "";
|
|
1658
1812
|
const lastId = features.length > 0 ? String(features[features.length - 1]?.id ?? "") : "";
|
|
1659
|
-
const bbox = normalizeBboxFromData(
|
|
1813
|
+
const bbox = normalizeBboxFromData(safeData);
|
|
1660
1814
|
const idsSample = buildIdsSample(features);
|
|
1661
1815
|
const signature = bbox ? `${layerId}|${features.length}|${firstId}|${lastId}|${idsSample}|${bbox[0]}|${bbox[1]}|${bbox[2]}|${bbox[3]}` : `${layerId}|${features.length}|${firstId}|${lastId}|${idsSample}`;
|
|
1662
1816
|
const signatureToken = signature.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
@@ -1664,25 +1818,40 @@ var LayerGeoJson = ({
|
|
|
1664
1818
|
dataVersionRef.current += 1;
|
|
1665
1819
|
prevSignatureRef.current = signature;
|
|
1666
1820
|
}
|
|
1667
|
-
const fillData = fillFeatures.length > 0 ? buildFeatureCollection(fillFeatures) : null;
|
|
1668
|
-
const pointsData = pointFeatures.length > 0 ? buildFeatureCollection(pointFeatures) : null;
|
|
1821
|
+
const fillData = (0, import_react.useMemo)(() => fillFeatures.length > 0 ? buildFeatureCollection(fillFeatures) : null, [fillFeatures]);
|
|
1822
|
+
const pointsData = (0, import_react.useMemo)(() => pointFeatures.length > 0 ? buildFeatureCollection(pointFeatures) : null, [pointFeatures]);
|
|
1669
1823
|
const clusterLayerRef = (0, import_react.useRef)(null);
|
|
1670
1824
|
const canCluster = typeof import_leaflet.default.markerClusterGroup === "function";
|
|
1825
|
+
const resolvedFillPane = (0, import_react.useMemo)(
|
|
1826
|
+
() => ensurePaneName(mapInstance, fillPaneName, "overlayPane"),
|
|
1827
|
+
[fillPaneName, mapInstance]
|
|
1828
|
+
);
|
|
1829
|
+
const resolvedPointsPane = (0, import_react.useMemo)(
|
|
1830
|
+
() => ensurePaneName(mapInstance, pointsPaneName, "markerPane"),
|
|
1831
|
+
[mapInstance, pointsPaneName]
|
|
1832
|
+
);
|
|
1671
1833
|
(0, import_react.useEffect)(() => {
|
|
1672
1834
|
if (!mapInstance || !panesReady || !pointsData || !canCluster) return;
|
|
1673
1835
|
const markerClusterGroup = import_leaflet.default.markerClusterGroup;
|
|
1674
|
-
const
|
|
1836
|
+
const clusterPaneName = resolvedPointsPane;
|
|
1837
|
+
const clusterLayer = clusterLayerRef.current ?? markerClusterGroup({ pane: clusterPaneName, clusterPane: clusterPaneName });
|
|
1675
1838
|
clusterLayerRef.current = clusterLayer;
|
|
1676
1839
|
if (!mapInstance.hasLayer(clusterLayer)) {
|
|
1677
1840
|
mapInstance.addLayer(clusterLayer);
|
|
1678
1841
|
}
|
|
1679
1842
|
clusterLayer.clearLayers();
|
|
1680
1843
|
const geoJsonLayer = import_leaflet.default.geoJSON(pointsData, {
|
|
1681
|
-
pointToLayer: (feature, latlng) =>
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1844
|
+
pointToLayer: (feature, latlng) => {
|
|
1845
|
+
if (!isValidLatLng(latlng)) {
|
|
1846
|
+
return createInvisibleFallbackClusterMarker();
|
|
1847
|
+
}
|
|
1848
|
+
const style = styleFn(feature, layerType, baseOpacity);
|
|
1849
|
+
return import_leaflet.default.marker(latlng, {
|
|
1850
|
+
icon: createPointDivIcon(style, isMobile),
|
|
1851
|
+
pane: clusterPaneName,
|
|
1852
|
+
interactive: true
|
|
1853
|
+
});
|
|
1854
|
+
},
|
|
1686
1855
|
onEachFeature
|
|
1687
1856
|
});
|
|
1688
1857
|
clusterLayer.addLayer(geoJsonLayer);
|
|
@@ -1701,7 +1870,7 @@ var LayerGeoJson = ({
|
|
|
1701
1870
|
onEachFeature,
|
|
1702
1871
|
panesReady,
|
|
1703
1872
|
pointsData,
|
|
1704
|
-
|
|
1873
|
+
resolvedPointsPane,
|
|
1705
1874
|
styleFn
|
|
1706
1875
|
]);
|
|
1707
1876
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
@@ -1709,7 +1878,7 @@ var LayerGeoJson = ({
|
|
|
1709
1878
|
import_react_leaflet.GeoJSON,
|
|
1710
1879
|
{
|
|
1711
1880
|
data: fillData,
|
|
1712
|
-
pane:
|
|
1881
|
+
pane: resolvedFillPane,
|
|
1713
1882
|
style: (feature) => styleFn(feature, layerType, baseOpacity),
|
|
1714
1883
|
onEachFeature: (feature, layer) => {
|
|
1715
1884
|
onEachFeature(feature, layer);
|
|
@@ -1722,11 +1891,16 @@ var LayerGeoJson = ({
|
|
|
1722
1891
|
import_react_leaflet.GeoJSON,
|
|
1723
1892
|
{
|
|
1724
1893
|
data: pointsData,
|
|
1725
|
-
pane:
|
|
1726
|
-
pointToLayer: (feature, latlng) =>
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1894
|
+
pane: resolvedPointsPane,
|
|
1895
|
+
pointToLayer: (feature, latlng) => {
|
|
1896
|
+
if (!isValidLatLng(latlng)) {
|
|
1897
|
+
return createInvisibleFallbackMarker();
|
|
1898
|
+
}
|
|
1899
|
+
return import_leaflet.default.circleMarker(latlng, {
|
|
1900
|
+
radius: isMobile ? 8 : 6,
|
|
1901
|
+
...styleFn(feature, layerType, baseOpacity)
|
|
1902
|
+
});
|
|
1903
|
+
},
|
|
1730
1904
|
onEachFeature
|
|
1731
1905
|
},
|
|
1732
1906
|
`points-${layerId}-${signatureToken}-v${dataVersionRef.current}`
|
|
@@ -1808,7 +1982,22 @@ function useGeolocation(options) {
|
|
|
1808
1982
|
var import_leaflet2 = __toESM(require("leaflet"));
|
|
1809
1983
|
var POPUP_STYLE_ID = "zenit-leaflet-popup-styles";
|
|
1810
1984
|
var POPUP_EXCLUDED_KEYS = /* @__PURE__ */ new Set(["geom", "geometry", "_private"]);
|
|
1811
|
-
var
|
|
1985
|
+
var POPUP_TITLE_KEYS = ["id", "nombre", "name", "title", "titulo", "cluster"];
|
|
1986
|
+
var POPUP_BADGE_KEYS = ["tipo", "type", "category", "categoria", "estado", "status"];
|
|
1987
|
+
var POPUP_DESCRIPTION_KEYS = ["descripcion", "description", "desc"];
|
|
1988
|
+
var CURRENCY_KEYWORDS = [
|
|
1989
|
+
"capital",
|
|
1990
|
+
"monto",
|
|
1991
|
+
"precio",
|
|
1992
|
+
"costo",
|
|
1993
|
+
"valor",
|
|
1994
|
+
"ingreso",
|
|
1995
|
+
"egreso",
|
|
1996
|
+
"saldo",
|
|
1997
|
+
"subtotal",
|
|
1998
|
+
"venta",
|
|
1999
|
+
"compra"
|
|
2000
|
+
];
|
|
1812
2001
|
var DESKTOP_POPUP_DIMENSIONS = { maxWidth: 360, minWidth: 280, maxHeight: 520 };
|
|
1813
2002
|
var MOBILE_POPUP_DIMENSIONS = { maxWidth: 300, minWidth: 240, maxHeight: 420 };
|
|
1814
2003
|
var ZENIT_LEAFLET_POPUP_STYLES = `
|
|
@@ -1926,6 +2115,68 @@ function escapeHtml(value) {
|
|
|
1926
2115
|
function formatLabel(key) {
|
|
1927
2116
|
return key.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").trim();
|
|
1928
2117
|
}
|
|
2118
|
+
function isHexColor(value) {
|
|
2119
|
+
return /^#([0-9A-F]{3}){1,2}$/i.test(value.trim());
|
|
2120
|
+
}
|
|
2121
|
+
function isCurrencyField(key) {
|
|
2122
|
+
const normalized = key.trim().toLowerCase();
|
|
2123
|
+
return CURRENCY_KEYWORDS.some((keyword) => normalized.includes(keyword));
|
|
2124
|
+
}
|
|
2125
|
+
function formatCurrency(value) {
|
|
2126
|
+
return new Intl.NumberFormat("es-GT", { style: "currency", currency: "GTQ" }).format(value);
|
|
2127
|
+
}
|
|
2128
|
+
function getBadgeForProperty(key, value) {
|
|
2129
|
+
if (value === null || value === void 0) return null;
|
|
2130
|
+
const normalizedKey = key.trim().toLowerCase();
|
|
2131
|
+
const normalizedValue = String(value).trim().toLowerCase();
|
|
2132
|
+
const byType = {
|
|
2133
|
+
mora: { label: "Mora", bg: "#fee2e2", color: "#b91c1c", border: "#fecaca" },
|
|
2134
|
+
castigo: { label: "Castigo", bg: "#ffedd5", color: "#c2410c", border: "#fed7aa" },
|
|
2135
|
+
colocacion: { label: "Colocaci\xF3n", bg: "#dbeafe", color: "#1d4ed8", border: "#bfdbfe" },
|
|
2136
|
+
sano: { label: "Sano", bg: "#dcfce7", color: "#15803d", border: "#bbf7d0" }
|
|
2137
|
+
};
|
|
2138
|
+
const byStatus = {
|
|
2139
|
+
activo: { label: "Activo", bg: "#dcfce7", color: "#15803d", border: "#bbf7d0" },
|
|
2140
|
+
inactivo: { label: "Inactivo", bg: "#fef9c3", color: "#a16207", border: "#fde68a" },
|
|
2141
|
+
pendiente: { label: "Pendiente", bg: "#fef9c3", color: "#a16207", border: "#fde68a" },
|
|
2142
|
+
cancelado: { label: "Cancelado", bg: "#fee2e2", color: "#b91c1c", border: "#fecaca" },
|
|
2143
|
+
rechazado: { label: "Rechazado", bg: "#fee2e2", color: "#b91c1c", border: "#fecaca" }
|
|
2144
|
+
};
|
|
2145
|
+
if (["tipo", "type", "category", "categoria"].includes(normalizedKey)) {
|
|
2146
|
+
return byType[normalizedValue] ?? {
|
|
2147
|
+
label: String(value),
|
|
2148
|
+
bg: "#e0e7ff",
|
|
2149
|
+
color: "#4338ca",
|
|
2150
|
+
border: "#c7d2fe"
|
|
2151
|
+
};
|
|
2152
|
+
}
|
|
2153
|
+
if (["estado", "status"].includes(normalizedKey)) {
|
|
2154
|
+
return byStatus[normalizedValue] ?? {
|
|
2155
|
+
label: String(value),
|
|
2156
|
+
bg: "#f1f5f9",
|
|
2157
|
+
color: "#475569",
|
|
2158
|
+
border: "#cbd5e1"
|
|
2159
|
+
};
|
|
2160
|
+
}
|
|
2161
|
+
return null;
|
|
2162
|
+
}
|
|
2163
|
+
function findHeaderProperties(properties) {
|
|
2164
|
+
const entries = Object.entries(properties);
|
|
2165
|
+
const titleEntry = entries.find(
|
|
2166
|
+
([key, value]) => POPUP_TITLE_KEYS.includes(key.trim().toLowerCase()) && String(value ?? "").trim().length > 0
|
|
2167
|
+
);
|
|
2168
|
+
const badgeEntry = entries.find(
|
|
2169
|
+
([key, value]) => POPUP_BADGE_KEYS.includes(key.trim().toLowerCase()) && String(value ?? "").trim().length > 0
|
|
2170
|
+
);
|
|
2171
|
+
const descriptionEntry = entries.find(
|
|
2172
|
+
([key, value]) => POPUP_DESCRIPTION_KEYS.includes(key.trim().toLowerCase()) && String(value ?? "").trim().length > 0
|
|
2173
|
+
);
|
|
2174
|
+
return {
|
|
2175
|
+
title: titleEntry ? { key: titleEntry[0], value: String(titleEntry[1]).trim() } : void 0,
|
|
2176
|
+
badge: badgeEntry ? { key: badgeEntry[0], value: badgeEntry[1] } : void 0,
|
|
2177
|
+
description: descriptionEntry ? { key: descriptionEntry[0], value: String(descriptionEntry[1]).trim() } : void 0
|
|
2178
|
+
};
|
|
2179
|
+
}
|
|
1929
2180
|
function renderPopupValue(value) {
|
|
1930
2181
|
if (value === null || value === void 0) return '<span style="color:#94a3b8;">Sin datos</span>';
|
|
1931
2182
|
if (value instanceof Date) {
|
|
@@ -1951,12 +2202,8 @@ function renderPopupValue(value) {
|
|
|
1951
2202
|
return escapeHtml(String(value));
|
|
1952
2203
|
}
|
|
1953
2204
|
function extractPopupHeader(properties) {
|
|
1954
|
-
const
|
|
1955
|
-
|
|
1956
|
-
const normalized = key.trim().toLowerCase();
|
|
1957
|
-
return POPUP_HEADER_KEYS.includes(normalized) && value.trim().length > 0;
|
|
1958
|
-
});
|
|
1959
|
-
return entry ? entry[1].trim() : null;
|
|
2205
|
+
const header = findHeaderProperties(properties);
|
|
2206
|
+
return header.title?.value ?? null;
|
|
1960
2207
|
}
|
|
1961
2208
|
function shouldIncludePopupEntry(key, value) {
|
|
1962
2209
|
if (!key) return false;
|
|
@@ -1969,21 +2216,36 @@ function shouldIncludePopupEntry(key, value) {
|
|
|
1969
2216
|
return true;
|
|
1970
2217
|
}
|
|
1971
2218
|
function createPopupContent(properties) {
|
|
1972
|
-
const
|
|
2219
|
+
const header = findHeaderProperties(properties);
|
|
2220
|
+
const headerText = header.title?.value ?? extractPopupHeader(properties);
|
|
2221
|
+
const usedKeys = new Set([header.title?.key, header.badge?.key, header.description?.key].filter(Boolean));
|
|
2222
|
+
const badge = header.badge ? getBadgeForProperty(header.badge.key, header.badge.value) : null;
|
|
2223
|
+
const colorValue = properties.color;
|
|
2224
|
+
const colorBar = typeof colorValue === "string" && isHexColor(colorValue) ? `<div style="height:6px; border-radius:8px; margin:-2px -2px 10px; background:linear-gradient(90deg, ${colorValue}, rgba(255,255,255,0.95));"></div>` : "";
|
|
1973
2225
|
const entries = Object.entries(properties).filter(([key, value]) => {
|
|
1974
2226
|
if (!shouldIncludePopupEntry(key, value)) return false;
|
|
1975
|
-
if (
|
|
2227
|
+
if (usedKeys.has(key)) return false;
|
|
1976
2228
|
return true;
|
|
1977
2229
|
});
|
|
1978
2230
|
if (entries.length === 0) {
|
|
1979
2231
|
return '<div style="padding:8px 0; color:#64748b; text-align:center;">Sin datos disponibles</div>';
|
|
1980
2232
|
}
|
|
1981
|
-
const headerHtml = headerText ? `<div style="
|
|
1982
|
-
|
|
2233
|
+
const headerHtml = headerText || badge ? `<div style="display:flex; justify-content:space-between; gap:8px; align-items:flex-start; margin-bottom:8px;">
|
|
2234
|
+
<div style="font-weight:700; font-size:14px; color:#0f172a;">${escapeHtml(headerText ?? "Detalle")}</div>
|
|
2235
|
+
${badge ? `<span style="display:inline-flex; padding:2px 8px; border-radius:999px; font-size:11px; font-weight:700; background:${badge.bg}; color:${badge.color}; border:1px solid ${badge.border}; white-space:nowrap;">${escapeHtml(
|
|
2236
|
+
badge.label
|
|
2237
|
+
)}</span>` : ""}
|
|
2238
|
+
</div>` : "";
|
|
2239
|
+
const descriptionHtml = header.description ? `<div style="margin-bottom:10px; padding:8px 10px; background:#f8fafc; border-left:3px solid #38bdf8; border-radius:6px; color:#334155; font-size:12px;">${escapeHtml(
|
|
2240
|
+
header.description.value
|
|
1983
2241
|
)}</div>` : "";
|
|
1984
2242
|
const rowsHtml = entries.map(([key, value]) => {
|
|
1985
2243
|
const label = escapeHtml(formatLabel(key));
|
|
1986
|
-
const
|
|
2244
|
+
const normalizedKey = key.trim().toLowerCase();
|
|
2245
|
+
let valueHtml = renderPopupValue(value);
|
|
2246
|
+
if (typeof value === "number") {
|
|
2247
|
+
valueHtml = isCurrencyField(normalizedKey) ? `<span style="color:#15803d; font-weight:700;">${escapeHtml(formatCurrency(value))}</span>` : `<span style="color:#0369a1; font-weight:600;">${escapeHtml(value.toLocaleString("es-GT"))}</span>`;
|
|
2248
|
+
}
|
|
1987
2249
|
return `
|
|
1988
2250
|
<div style="display:grid; grid-template-columns:minmax(90px, 35%) 1fr; gap:8px; padding:6px 0; border-bottom:1px solid #e2e8f0;">
|
|
1989
2251
|
<div style="font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:0.04em; color:#64748b;">${label}</div>
|
|
@@ -1991,7 +2253,7 @@ function createPopupContent(properties) {
|
|
|
1991
2253
|
</div>
|
|
1992
2254
|
`;
|
|
1993
2255
|
}).join("");
|
|
1994
|
-
return `<div>${headerHtml}${rowsHtml}</div>`;
|
|
2256
|
+
return `<div>${colorBar}${headerHtml}${descriptionHtml}${rowsHtml}</div>`;
|
|
1995
2257
|
}
|
|
1996
2258
|
function isPolygonType(layerType, geometryType) {
|
|
1997
2259
|
const candidate = (layerType ?? geometryType ?? "").toLowerCase();
|
|
@@ -2001,21 +2263,27 @@ function calculateZoomBasedOpacity(zoom, baseOpacity, layerType, geometryType) {
|
|
|
2001
2263
|
if (!isPolygonType(layerType, geometryType)) return clampOpacity4(baseOpacity);
|
|
2002
2264
|
const minZoom = 11;
|
|
2003
2265
|
const maxZoom = 15;
|
|
2004
|
-
const minFactor = 0.
|
|
2266
|
+
const minFactor = 0.5;
|
|
2005
2267
|
if (maxZoom <= minZoom) return clampOpacity4(baseOpacity * minFactor);
|
|
2006
2268
|
const t = clampNumber2((zoom - minZoom) / (maxZoom - minZoom), 0, 1);
|
|
2007
2269
|
const factor = 1 - (1 - minFactor) * t;
|
|
2008
2270
|
return clampOpacity4(baseOpacity * factor);
|
|
2009
2271
|
}
|
|
2010
2272
|
function layerStyleToLeaflet(options) {
|
|
2011
|
-
const { baseOpacity, zoom, layerStyle, geometryType, layerType } = options;
|
|
2273
|
+
const { baseOpacity, zoom, layerStyle, geometryType, layerType, properties } = options;
|
|
2012
2274
|
const sanitizedOpacity = clampOpacity4(baseOpacity);
|
|
2013
2275
|
const zoomOpacity = calculateZoomBasedOpacity(zoom, sanitizedOpacity, layerType, geometryType);
|
|
2014
|
-
const
|
|
2276
|
+
const badgeForType = getBadgeForProperty("type", properties?.type ?? properties?.tipo ?? properties?.category ?? properties?.categoria);
|
|
2277
|
+
const badgeForStatus = getBadgeForProperty("status", properties?.status ?? properties?.estado);
|
|
2278
|
+
const featureColor = (typeof properties?.color === "string" && properties.color.trim().length > 0 ? properties.color : void 0) ?? badgeForType?.color ?? badgeForStatus?.color;
|
|
2279
|
+
const styleFillOpacity = typeof layerStyle?.fillOpacity === "number" ? clampOpacity4(layerStyle.fillOpacity) : 0.65;
|
|
2280
|
+
const fallbackColor = "#2563eb";
|
|
2281
|
+
const resolvedColor = featureColor ?? layerStyle?.color ?? layerStyle?.fillColor ?? fallbackColor;
|
|
2282
|
+
const resolvedFillColor = featureColor ?? layerStyle?.fillColor ?? layerStyle?.color ?? fallbackColor;
|
|
2015
2283
|
return {
|
|
2016
|
-
color:
|
|
2284
|
+
color: resolvedColor,
|
|
2017
2285
|
weight: layerStyle?.weight ?? 2,
|
|
2018
|
-
fillColor:
|
|
2286
|
+
fillColor: resolvedFillColor,
|
|
2019
2287
|
opacity: clampOpacity4(Math.max(0.35, zoomOpacity * 0.9)),
|
|
2020
2288
|
fillOpacity: clampOpacity4(zoomOpacity * styleFillOpacity)
|
|
2021
2289
|
};
|
|
@@ -2413,6 +2681,7 @@ var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
|
2413
2681
|
var DEFAULT_CENTER = [0, 0];
|
|
2414
2682
|
var DEFAULT_ZOOM = 3;
|
|
2415
2683
|
var LABELS_PANE_NAME = "zenit-labels-pane";
|
|
2684
|
+
var DEV_MODE2 = typeof process !== "undefined" && process.env.NODE_ENV !== "production";
|
|
2416
2685
|
function isRecord(value) {
|
|
2417
2686
|
return typeof value === "object" && value !== null;
|
|
2418
2687
|
}
|
|
@@ -2436,6 +2705,8 @@ function getFeatureLayerId(feature) {
|
|
|
2436
2705
|
return layerId;
|
|
2437
2706
|
}
|
|
2438
2707
|
var DESCRIPTION_KEYS = /* @__PURE__ */ new Set(["descripcion", "description"]);
|
|
2708
|
+
var POINT_GEOMETRY_TYPES2 = /* @__PURE__ */ new Set(["Point", "MultiPoint"]);
|
|
2709
|
+
var POLYGON_GEOMETRY_TYPES = /* @__PURE__ */ new Set(["Polygon", "MultiPolygon"]);
|
|
2439
2710
|
function normalizeDescriptionValue(value) {
|
|
2440
2711
|
if (value === void 0 || value === null) return null;
|
|
2441
2712
|
if (typeof value === "string") {
|
|
@@ -2455,6 +2726,31 @@ function extractDescriptionValue(properties) {
|
|
|
2455
2726
|
if (!matches) return null;
|
|
2456
2727
|
return normalizeDescriptionValue(matches[1]);
|
|
2457
2728
|
}
|
|
2729
|
+
function normalizeLayerId(value) {
|
|
2730
|
+
if (value === null || value === void 0) return null;
|
|
2731
|
+
const normalized = String(value).trim();
|
|
2732
|
+
return normalized ? normalized : null;
|
|
2733
|
+
}
|
|
2734
|
+
function isLayerIdMatch(a, b) {
|
|
2735
|
+
const normalizedA = normalizeLayerId(a);
|
|
2736
|
+
const normalizedB = normalizeLayerId(b);
|
|
2737
|
+
return normalizedA !== null && normalizedB !== null && normalizedA === normalizedB;
|
|
2738
|
+
}
|
|
2739
|
+
function getClickIntent(params) {
|
|
2740
|
+
const geometryType = params.feature?.geometry?.type;
|
|
2741
|
+
if (geometryType && POINT_GEOMETRY_TYPES2.has(geometryType)) return "point";
|
|
2742
|
+
if (params.leafletLayer instanceof import_leaflet4.default.Marker) return "point";
|
|
2743
|
+
if (geometryType && POLYGON_GEOMETRY_TYPES.has(geometryType)) return "polygon";
|
|
2744
|
+
if (params.leafletLayer instanceof import_leaflet4.default.Path) return "polygon";
|
|
2745
|
+
return "unknown";
|
|
2746
|
+
}
|
|
2747
|
+
function candidateLayerId(candidate) {
|
|
2748
|
+
return getFeatureLayerId(candidate);
|
|
2749
|
+
}
|
|
2750
|
+
function isCandidateGeometryType(candidate, allowedTypes) {
|
|
2751
|
+
const geometryType = candidate?.geometry?.type;
|
|
2752
|
+
return Boolean(geometryType && allowedTypes.has(geometryType));
|
|
2753
|
+
}
|
|
2458
2754
|
function getFeatureStyleOverrides(feature) {
|
|
2459
2755
|
const candidate = feature?.properties?._style;
|
|
2460
2756
|
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return null;
|
|
@@ -2472,17 +2768,50 @@ function buildFeaturePopupHtml(feature) {
|
|
|
2472
2768
|
const rendered = createPopupContent(properties);
|
|
2473
2769
|
return rendered ? rendered : null;
|
|
2474
2770
|
}
|
|
2475
|
-
function pickIntersectFeature(
|
|
2771
|
+
function pickIntersectFeature(params) {
|
|
2772
|
+
const { baseFeature, candidates, expectedLayerId, clickIntent } = params;
|
|
2476
2773
|
if (!Array.isArray(candidates) || candidates.length === 0) return null;
|
|
2774
|
+
const pickByGeometry = (pool, intent) => {
|
|
2775
|
+
const preferredTypes = intent === "point" ? POINT_GEOMETRY_TYPES2 : intent === "polygon" ? POLYGON_GEOMETRY_TYPES : null;
|
|
2776
|
+
if (!preferredTypes) return null;
|
|
2777
|
+
const found = pool.find((candidate) => isCandidateGeometryType(candidate, preferredTypes));
|
|
2778
|
+
if (!found) return null;
|
|
2779
|
+
return { feature: found, reason: `geometry:${intent}` };
|
|
2780
|
+
};
|
|
2781
|
+
const getResult = (feature, reason) => ({
|
|
2782
|
+
feature,
|
|
2783
|
+
selectedIdx: candidates.findIndex((candidate) => candidate === feature),
|
|
2784
|
+
reason
|
|
2785
|
+
});
|
|
2786
|
+
const sameLayer = candidates.filter(
|
|
2787
|
+
(candidate) => isLayerIdMatch(candidateLayerId(candidate), expectedLayerId)
|
|
2788
|
+
);
|
|
2789
|
+
const geometryFromSameLayer = pickByGeometry(sameLayer, clickIntent);
|
|
2790
|
+
if (geometryFromSameLayer?.feature) {
|
|
2791
|
+
return getResult(geometryFromSameLayer.feature, `sameLayer>${geometryFromSameLayer.reason}`);
|
|
2792
|
+
}
|
|
2793
|
+
if (sameLayer.length > 0) {
|
|
2794
|
+
const sameLayerWithDescription = sameLayer.find(
|
|
2795
|
+
(candidate) => extractDescriptionValue(candidate?.properties)
|
|
2796
|
+
);
|
|
2797
|
+
if (sameLayerWithDescription) {
|
|
2798
|
+
return getResult(sameLayerWithDescription, "sameLayer>description");
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
const geometryFallback = pickByGeometry(candidates, clickIntent);
|
|
2802
|
+
if (geometryFallback?.feature) {
|
|
2803
|
+
return getResult(geometryFallback.feature, `anyLayer>${geometryFallback.reason}`);
|
|
2804
|
+
}
|
|
2477
2805
|
const baseId = baseFeature?.id;
|
|
2478
2806
|
if (baseId !== void 0 && baseId !== null) {
|
|
2479
2807
|
const matchById = candidates.find((candidate) => candidate?.id === baseId);
|
|
2480
|
-
if (matchById) return matchById;
|
|
2808
|
+
if (matchById) return getResult(matchById, "fallback>baseId");
|
|
2481
2809
|
}
|
|
2482
2810
|
const matchWithDescription = candidates.find(
|
|
2483
2811
|
(candidate) => extractDescriptionValue(candidate?.properties)
|
|
2484
2812
|
);
|
|
2485
|
-
return matchWithDescription
|
|
2813
|
+
if (matchWithDescription) return getResult(matchWithDescription, "fallback>description");
|
|
2814
|
+
return getResult(candidates[0], "fallback>first");
|
|
2486
2815
|
}
|
|
2487
2816
|
function normalizeCenterTuple(center) {
|
|
2488
2817
|
if (!center) return null;
|
|
@@ -2889,7 +3218,8 @@ var ZenitMap = (0, import_react5.forwardRef)(({
|
|
|
2889
3218
|
}, [currentZoom, decoratedLayers, labelKeyIndex, layerMetaIndex, resolveLayerStyle]);
|
|
2890
3219
|
const ensureLayerPanes = (0, import_react5.useCallback)(
|
|
2891
3220
|
(targetMap, targetLayers) => {
|
|
2892
|
-
const
|
|
3221
|
+
const fillBaseZIndex = 400;
|
|
3222
|
+
const pointsBaseZIndex = 700;
|
|
2893
3223
|
targetLayers.forEach((layer) => {
|
|
2894
3224
|
const order = Number.isFinite(layer.displayOrder) ? layer.displayOrder : 0;
|
|
2895
3225
|
const orderOffset = Math.max(0, Math.min(order, 150));
|
|
@@ -2897,8 +3227,8 @@ var ZenitMap = (0, import_react5.forwardRef)(({
|
|
|
2897
3227
|
const pointPaneName = `zenit-layer-${layer.layerId}-points`;
|
|
2898
3228
|
const fillPane = targetMap.getPane(fillPaneName) ?? targetMap.createPane(fillPaneName);
|
|
2899
3229
|
const pointPane = targetMap.getPane(pointPaneName) ?? targetMap.createPane(pointPaneName);
|
|
2900
|
-
fillPane.style.zIndex = String(
|
|
2901
|
-
pointPane.style.zIndex = String(
|
|
3230
|
+
fillPane.style.zIndex = String(fillBaseZIndex + orderOffset);
|
|
3231
|
+
pointPane.style.zIndex = String(pointsBaseZIndex + orderOffset);
|
|
2902
3232
|
});
|
|
2903
3233
|
},
|
|
2904
3234
|
[]
|
|
@@ -2916,114 +3246,149 @@ var ZenitMap = (0, import_react5.forwardRef)(({
|
|
|
2916
3246
|
setPanesReady(false);
|
|
2917
3247
|
return;
|
|
2918
3248
|
}
|
|
2919
|
-
if (orderedLayers.length === 0) {
|
|
3249
|
+
if (orderedLayers.length === 0 && !overlayGeojson) {
|
|
2920
3250
|
return;
|
|
2921
3251
|
}
|
|
2922
3252
|
const layerTargets = orderedLayers.map((layer) => ({
|
|
2923
3253
|
layerId: layer.mapLayer.layerId,
|
|
2924
3254
|
displayOrder: layer.displayOrder
|
|
2925
3255
|
}));
|
|
3256
|
+
if (overlayGeojson) {
|
|
3257
|
+
layerTargets.push({ layerId: "overlay-geojson", displayOrder: 999 });
|
|
3258
|
+
}
|
|
2926
3259
|
ensureLayerPanes(mapInstance, layerTargets);
|
|
2927
3260
|
const first = layerTargets[0];
|
|
2928
|
-
const testPane = mapInstance.getPane(`zenit-layer-${first.layerId}-fill`);
|
|
3261
|
+
const testPane = first ? mapInstance.getPane(`zenit-layer-${first.layerId}-fill`) : null;
|
|
2929
3262
|
const labelsPane = mapInstance.getPane(LABELS_PANE_NAME);
|
|
2930
3263
|
if (testPane && labelsPane) {
|
|
2931
3264
|
setPanesReady(true);
|
|
2932
3265
|
}
|
|
2933
|
-
}, [mapInstance, orderedLayers, ensureLayerPanes]);
|
|
2934
|
-
const overlayOnEachFeature = (0, import_react5.
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
if (isPointFeature && layer.bindTooltip) {
|
|
2963
|
-
layer.bindTooltip("Click para ver detalle", {
|
|
2964
|
-
sticky: true,
|
|
2965
|
-
direction: "top",
|
|
2966
|
-
opacity: 0.9,
|
|
2967
|
-
className: "zenit-map-tooltip"
|
|
3266
|
+
}, [mapInstance, orderedLayers, overlayGeojson, ensureLayerPanes]);
|
|
3267
|
+
const overlayOnEachFeature = (0, import_react5.useCallback)((feature, layer) => {
|
|
3268
|
+
const layerId = getFeatureLayerId(feature) ?? void 0;
|
|
3269
|
+
const geometryType = feature?.geometry?.type;
|
|
3270
|
+
const clickIntent = getClickIntent({ feature, leafletLayer: layer });
|
|
3271
|
+
const isPointFeature = clickIntent === "point";
|
|
3272
|
+
const originalStyle = layer instanceof import_leaflet4.default.Path ? {
|
|
3273
|
+
color: layer.options.color,
|
|
3274
|
+
weight: layer.options.weight,
|
|
3275
|
+
fillColor: layer.options.fillColor,
|
|
3276
|
+
opacity: layer.options.opacity,
|
|
3277
|
+
fillOpacity: layer.options.fillOpacity
|
|
3278
|
+
} : null;
|
|
3279
|
+
const originalRadius = layer instanceof import_leaflet4.default.CircleMarker ? layer.getRadius() : null;
|
|
3280
|
+
if (featureInfoMode === "popup") {
|
|
3281
|
+
const content = buildFeaturePopupHtml(feature);
|
|
3282
|
+
if (content) {
|
|
3283
|
+
const { maxWidth, minWidth, maxHeight } = getPopupDimensions();
|
|
3284
|
+
layer.bindPopup(content, {
|
|
3285
|
+
maxWidth,
|
|
3286
|
+
minWidth,
|
|
3287
|
+
maxHeight,
|
|
3288
|
+
className: "custom-leaflet-popup",
|
|
3289
|
+
autoClose: true,
|
|
3290
|
+
closeOnClick: true,
|
|
3291
|
+
autoPan: true,
|
|
3292
|
+
closeButton: true,
|
|
3293
|
+
keepInView: true,
|
|
3294
|
+
autoPanPadding: [50, 50]
|
|
2968
3295
|
});
|
|
2969
3296
|
}
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
geometry: feature.geometry
|
|
2978
|
-
}).then((response) => {
|
|
2979
|
-
const geo = extractGeoJsonFeatureCollection(response);
|
|
2980
|
-
const candidates = geo?.features ?? [];
|
|
2981
|
-
const resolved = pickIntersectFeature(feature, candidates);
|
|
2982
|
-
if (!resolved?.properties) return;
|
|
2983
|
-
const mergedProperties = {
|
|
2984
|
-
...trackedFeature.properties ?? {},
|
|
2985
|
-
...resolved.properties
|
|
2986
|
-
};
|
|
2987
|
-
trackedFeature.properties = mergedProperties;
|
|
2988
|
-
const updatedHtml = buildFeaturePopupHtml({
|
|
2989
|
-
...feature,
|
|
2990
|
-
properties: mergedProperties
|
|
2991
|
-
});
|
|
2992
|
-
if (updatedHtml && layer.setPopupContent) {
|
|
2993
|
-
layer.setPopupContent(updatedHtml);
|
|
2994
|
-
}
|
|
2995
|
-
}).catch(() => {
|
|
2996
|
-
trackedFeature.__zenit_popup_loaded = false;
|
|
2997
|
-
});
|
|
2998
|
-
}
|
|
2999
|
-
}
|
|
3000
|
-
onFeatureClick?.(feature, layerId);
|
|
3297
|
+
}
|
|
3298
|
+
if (isPointFeature && layer.bindTooltip) {
|
|
3299
|
+
layer.bindTooltip("Click para ver detalle", {
|
|
3300
|
+
sticky: true,
|
|
3301
|
+
direction: "top",
|
|
3302
|
+
opacity: 0.9,
|
|
3303
|
+
className: "zenit-map-tooltip"
|
|
3001
3304
|
});
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3305
|
+
}
|
|
3306
|
+
layer.on("click", () => {
|
|
3307
|
+
if (featureInfoMode === "popup" && client && layerId !== void 0 && !extractDescriptionValue(feature?.properties) && feature?.geometry) {
|
|
3308
|
+
if (DEV_MODE2) {
|
|
3309
|
+
console.debug("[ZenitMap] click/intersect:start", {
|
|
3310
|
+
expectedLayerId: layerId,
|
|
3311
|
+
geometryType,
|
|
3312
|
+
clickIntent,
|
|
3313
|
+
leafletLayerType: layer?.constructor?.name,
|
|
3314
|
+
pane: layer?.options?.pane
|
|
3009
3315
|
});
|
|
3010
3316
|
}
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3317
|
+
const trackedFeature = feature;
|
|
3318
|
+
if (!trackedFeature.__zenit_popup_loaded) {
|
|
3319
|
+
trackedFeature.__zenit_popup_loaded = true;
|
|
3320
|
+
client.layers.getLayerGeoJsonIntersect({
|
|
3321
|
+
id: layerId,
|
|
3322
|
+
geometry: feature.geometry
|
|
3323
|
+
}).then((response) => {
|
|
3324
|
+
const geo = extractGeoJsonFeatureCollection(response);
|
|
3325
|
+
const candidates = geo?.features ?? [];
|
|
3326
|
+
const selection = pickIntersectFeature({
|
|
3327
|
+
baseFeature: feature,
|
|
3328
|
+
candidates,
|
|
3329
|
+
expectedLayerId: layerId,
|
|
3330
|
+
clickIntent
|
|
3331
|
+
});
|
|
3332
|
+
const resolved = selection?.feature;
|
|
3333
|
+
if (DEV_MODE2) {
|
|
3334
|
+
console.debug("[ZenitMap] click/intersect:result", {
|
|
3335
|
+
expectedLayerId: layerId,
|
|
3336
|
+
clickIntent,
|
|
3337
|
+
candidatesCount: candidates.length,
|
|
3338
|
+
candidates: candidates.map((candidate, idx) => ({
|
|
3339
|
+
idx,
|
|
3340
|
+
geomType: candidate?.geometry?.type,
|
|
3341
|
+
candidateLayerId: candidateLayerId(candidate),
|
|
3342
|
+
hasDescription: Boolean(extractDescriptionValue(candidate?.properties))
|
|
3343
|
+
})),
|
|
3344
|
+
selectedIdx: selection?.selectedIdx ?? -1,
|
|
3345
|
+
selectionRule: selection?.reason ?? "none"
|
|
3346
|
+
});
|
|
3347
|
+
}
|
|
3348
|
+
if (!resolved?.properties) return;
|
|
3349
|
+
const mergedProperties = {
|
|
3350
|
+
...trackedFeature.properties ?? {},
|
|
3351
|
+
...resolved.properties
|
|
3352
|
+
};
|
|
3353
|
+
trackedFeature.properties = mergedProperties;
|
|
3354
|
+
const updatedHtml = buildFeaturePopupHtml({
|
|
3355
|
+
...feature,
|
|
3356
|
+
properties: mergedProperties
|
|
3357
|
+
});
|
|
3358
|
+
if (updatedHtml && layer.setPopupContent) {
|
|
3359
|
+
layer.setPopupContent(updatedHtml);
|
|
3360
|
+
}
|
|
3361
|
+
}).catch(() => {
|
|
3362
|
+
trackedFeature.__zenit_popup_loaded = false;
|
|
3363
|
+
});
|
|
3022
3364
|
}
|
|
3023
|
-
}
|
|
3024
|
-
|
|
3365
|
+
}
|
|
3366
|
+
onFeatureClick?.(feature, layerId);
|
|
3367
|
+
});
|
|
3368
|
+
layer.on("mouseover", () => {
|
|
3369
|
+
if (layer instanceof import_leaflet4.default.Path && originalStyle) {
|
|
3370
|
+
layer.setStyle({
|
|
3371
|
+
...originalStyle,
|
|
3372
|
+
weight: (originalStyle.weight ?? 2) + 1,
|
|
3373
|
+
opacity: Math.min(1, (originalStyle.opacity ?? 1) + 0.2),
|
|
3374
|
+
fillOpacity: Math.min(1, (originalStyle.fillOpacity ?? 0.8) + 0.1)
|
|
3375
|
+
});
|
|
3376
|
+
}
|
|
3377
|
+
if (layer instanceof import_leaflet4.default.CircleMarker && typeof originalRadius === "number") {
|
|
3378
|
+
layer.setRadius(originalRadius + 1);
|
|
3379
|
+
}
|
|
3380
|
+
onFeatureHover?.(feature, layerId);
|
|
3381
|
+
});
|
|
3382
|
+
layer.on("mouseout", () => {
|
|
3383
|
+
if (layer instanceof import_leaflet4.default.Path && originalStyle) {
|
|
3384
|
+
layer.setStyle(originalStyle);
|
|
3385
|
+
}
|
|
3386
|
+
if (layer instanceof import_leaflet4.default.CircleMarker && typeof originalRadius === "number") {
|
|
3387
|
+
layer.setRadius(originalRadius);
|
|
3388
|
+
}
|
|
3389
|
+
});
|
|
3025
3390
|
}, [client, featureInfoMode, onFeatureClick, onFeatureHover]);
|
|
3026
|
-
const buildLayerStyle = (layerId, baseOpacity, feature, layerType) => {
|
|
3391
|
+
const buildLayerStyle = (0, import_react5.useCallback)((layerId, baseOpacity, feature, layerType) => {
|
|
3027
3392
|
const style = resolveLayerStyle(layerId);
|
|
3028
3393
|
const featureStyleOverrides = getFeatureStyleOverrides(feature);
|
|
3029
3394
|
const resolvedStyle = featureStyleOverrides ? { ...style ?? {}, ...featureStyleOverrides } : style;
|
|
@@ -3034,14 +3399,15 @@ var ZenitMap = (0, import_react5.forwardRef)(({
|
|
|
3034
3399
|
zoom: currentZoom,
|
|
3035
3400
|
layerStyle: resolvedStyle,
|
|
3036
3401
|
geometryType,
|
|
3037
|
-
layerType: resolvedLayerType
|
|
3402
|
+
layerType: resolvedLayerType,
|
|
3403
|
+
properties: feature?.properties ?? void 0
|
|
3038
3404
|
});
|
|
3039
|
-
};
|
|
3040
|
-
const makeStyleFnForLayer = (layerId) => {
|
|
3405
|
+
}, [currentZoom, resolveLayerStyle]);
|
|
3406
|
+
const makeStyleFnForLayer = (0, import_react5.useCallback)((layerId) => {
|
|
3041
3407
|
return (feature, layerType, baseOpacity) => {
|
|
3042
3408
|
return buildLayerStyle(layerId, baseOpacity ?? 1, feature, layerType);
|
|
3043
3409
|
};
|
|
3044
|
-
};
|
|
3410
|
+
}, [buildLayerStyle]);
|
|
3045
3411
|
(0, import_react5.useImperativeHandle)(ref, () => ({
|
|
3046
3412
|
setLayerOpacity: (layerId, opacity) => {
|
|
3047
3413
|
upsertUiOverride(layerId, { overrideOpacity: opacity });
|
|
@@ -3196,7 +3562,7 @@ var ZenitMap = (0, import_react5.forwardRef)(({
|
|
|
3196
3562
|
const layerType = layerState.layer?.layerType ?? layerState.mapLayer.layerType ?? void 0;
|
|
3197
3563
|
const labelKey = labelKeyIndex.get(String(layerState.mapLayer.layerId));
|
|
3198
3564
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react5.default.Fragment, { children: [
|
|
3199
|
-
layerState.data && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
3565
|
+
layerState.data && panesReady && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
3200
3566
|
LayerGeoJson,
|
|
3201
3567
|
{
|
|
3202
3568
|
layerId: layerState.mapLayer.layerId,
|
|
@@ -3239,7 +3605,7 @@ var ZenitMap = (0, import_react5.forwardRef)(({
|
|
|
3239
3605
|
)) : null
|
|
3240
3606
|
] }, layerState.mapLayer.layerId.toString());
|
|
3241
3607
|
}),
|
|
3242
|
-
overlayGeojson && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
3608
|
+
overlayGeojson && panesReady && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
3243
3609
|
LayerGeoJson,
|
|
3244
3610
|
{
|
|
3245
3611
|
layerId: "overlay-geojson",
|
|
@@ -3273,7 +3639,7 @@ var ZenitMap = (0, import_react5.forwardRef)(({
|
|
|
3273
3639
|
overflowY: "auto"
|
|
3274
3640
|
},
|
|
3275
3641
|
children: [
|
|
3276
|
-
overlayGeojson && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
3642
|
+
overlayGeojson && panesReady && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
3277
3643
|
"div",
|
|
3278
3644
|
{
|
|
3279
3645
|
style: {
|
|
@@ -3961,6 +4327,14 @@ var ZenitLayerManager = ({
|
|
|
3961
4327
|
return String(a.mapLayer.layerId).localeCompare(String(b.mapLayer.layerId));
|
|
3962
4328
|
});
|
|
3963
4329
|
}, [effectiveStates, layers, resolveFeatureCount]);
|
|
4330
|
+
const hasPrefilters = (0, import_react7.useMemo)(() => {
|
|
4331
|
+
const candidates = [...mapLayers ?? [], ...map?.mapLayers ?? []];
|
|
4332
|
+
return candidates.some((layer) => {
|
|
4333
|
+
const record = layer;
|
|
4334
|
+
const applied = record.appliedFilters ?? record.prefilters ?? record.initialFilters ?? record.filters ?? (record.layerConfig?.appliedFilters ?? record.layerConfig?.prefilters);
|
|
4335
|
+
return !!applied && typeof applied === "object" && Object.keys(applied).length > 0;
|
|
4336
|
+
});
|
|
4337
|
+
}, [map?.mapLayers, mapLayers]);
|
|
3964
4338
|
const filterableLayers = (0, import_react7.useMemo)(() => {
|
|
3965
4339
|
return decoratedLayers.filter((entry) => {
|
|
3966
4340
|
const prefilters = entry.mapLayer.layerConfig?.prefilters;
|
|
@@ -4033,6 +4407,11 @@ var ZenitLayerManager = ({
|
|
|
4033
4407
|
setSelectedFilterValue("");
|
|
4034
4408
|
}
|
|
4035
4409
|
}, [filterFields, selectedFilterField]);
|
|
4410
|
+
(0, import_react7.useEffect)(() => {
|
|
4411
|
+
if (hasPrefilters && activeTab === "filters") {
|
|
4412
|
+
setActiveTab("layers");
|
|
4413
|
+
}
|
|
4414
|
+
}, [activeTab, hasPrefilters]);
|
|
4036
4415
|
(0, import_react7.useEffect)(() => {
|
|
4037
4416
|
if (activeTab !== "filters") return;
|
|
4038
4417
|
if (!selectedFilterLayer || !selectedFilterField || !activeCatalogKey) return;
|
|
@@ -4433,7 +4812,7 @@ var ZenitLayerManager = ({
|
|
|
4433
4812
|
]
|
|
4434
4813
|
}
|
|
4435
4814
|
),
|
|
4436
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
4815
|
+
!hasPrefilters && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
4437
4816
|
"button",
|
|
4438
4817
|
{
|
|
4439
4818
|
type: "button",
|
|
@@ -4450,8 +4829,9 @@ var ZenitLayerManager = ({
|
|
|
4450
4829
|
)
|
|
4451
4830
|
] }),
|
|
4452
4831
|
panelVisible && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { padding: "12px 10px 18px", overflowY: "auto", flex: 1, minHeight: 0 }, children: [
|
|
4832
|
+
hasPrefilters && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "zlm-badge", style: { marginBottom: 10 }, children: "Este mapa ya incluye filtros preaplicados" }),
|
|
4453
4833
|
activeTab === "layers" && renderLayerCards(),
|
|
4454
|
-
activeTab === "filters" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "zlm-filter-panel", style: { display: "flex", flexDirection: "column", gap: 12, background: "#fff", border: "1px solid #e2e8f0", borderRadius: 12, padding: 12 }, children: !filterableLayers.length ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { color: "#64748b", fontSize: 13 }, children: "No hay filtros disponibles para las capas de este mapa." }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
4834
|
+
!hasPrefilters && activeTab === "filters" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "zlm-filter-panel", style: { display: "flex", flexDirection: "column", gap: 12, background: "#fff", border: "1px solid #e2e8f0", borderRadius: 12, padding: 12 }, children: !filterableLayers.length ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { color: "#64748b", fontSize: 13 }, children: "No hay filtros disponibles para las capas de este mapa." }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
4455
4835
|
filterableLayers.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { style: { display: "flex", flexDirection: "column", gap: 6, fontSize: 12, color: "#475569" }, children: [
|
|
4456
4836
|
"Capa",
|
|
4457
4837
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|