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/react/index.js
CHANGED
|
@@ -298,8 +298,122 @@ function getEffectiveLayerOpacity(baseOpacity, zoom, layerType, geometryType, op
|
|
|
298
298
|
var import_react = require("react");
|
|
299
299
|
var import_react_leaflet = require("react-leaflet");
|
|
300
300
|
var import_leaflet = __toESM(require("leaflet"));
|
|
301
|
+
|
|
302
|
+
// src/react/map/geojson-sanitize.ts
|
|
303
|
+
var warnedKeys = /* @__PURE__ */ new Set();
|
|
304
|
+
function isFiniteNumber(value) {
|
|
305
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
306
|
+
if (typeof value === "string") return Number.isFinite(Number(value));
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
function isValidLonLat(lon, lat) {
|
|
310
|
+
const lonN = typeof lon === "string" ? Number(lon) : lon;
|
|
311
|
+
const latN = typeof lat === "string" ? Number(lat) : lat;
|
|
312
|
+
if (!isFiniteNumber(lonN) || !isFiniteNumber(latN)) return false;
|
|
313
|
+
if (typeof lonN !== "number" || typeof latN !== "number") return false;
|
|
314
|
+
return lonN >= -180 && lonN <= 180 && latN >= -90 && latN <= 90;
|
|
315
|
+
}
|
|
316
|
+
function asFeatureCollection(input) {
|
|
317
|
+
const raw = input && typeof input === "object" && "data" in input ? input.data : input;
|
|
318
|
+
if (!raw || typeof raw !== "object") {
|
|
319
|
+
return { type: "FeatureCollection", features: [] };
|
|
320
|
+
}
|
|
321
|
+
if (raw.type === "FeatureCollection") {
|
|
322
|
+
const collection = raw;
|
|
323
|
+
const features = Array.isArray(collection.features) ? collection.features : [];
|
|
324
|
+
return { ...collection, features };
|
|
325
|
+
}
|
|
326
|
+
if (raw.type === "Feature") {
|
|
327
|
+
return {
|
|
328
|
+
type: "FeatureCollection",
|
|
329
|
+
features: [raw]
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
return { type: "FeatureCollection", features: [] };
|
|
333
|
+
}
|
|
334
|
+
function sanitizeFeature(feature) {
|
|
335
|
+
if (!feature || typeof feature !== "object") return null;
|
|
336
|
+
const geometry = feature.geometry;
|
|
337
|
+
if (!geometry || typeof geometry !== "object") return null;
|
|
338
|
+
const geometryType = geometry.type;
|
|
339
|
+
const coordinates = geometry.coordinates;
|
|
340
|
+
if (geometryType === "Point") {
|
|
341
|
+
if (!Array.isArray(coordinates) || coordinates.length < 2) return null;
|
|
342
|
+
return isValidLonLat(coordinates[0], coordinates[1]) ? feature : null;
|
|
343
|
+
}
|
|
344
|
+
if (geometryType === "MultiPoint") {
|
|
345
|
+
if (!Array.isArray(coordinates)) return null;
|
|
346
|
+
const validCoordinates = coordinates.filter((coordinate) => {
|
|
347
|
+
if (!Array.isArray(coordinate) || coordinate.length < 2) return false;
|
|
348
|
+
return isValidLonLat(coordinate[0], coordinate[1]);
|
|
349
|
+
});
|
|
350
|
+
if (validCoordinates.length === 0) return null;
|
|
351
|
+
if (validCoordinates.length === coordinates.length) {
|
|
352
|
+
return feature;
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
...feature,
|
|
356
|
+
geometry: {
|
|
357
|
+
...geometry,
|
|
358
|
+
coordinates: validCoordinates
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
if (geometryType === "LineString" || geometryType === "Polygon" || geometryType === "MultiPolygon" || geometryType === "MultiLineString") {
|
|
363
|
+
return Array.isArray(coordinates) && coordinates.length > 0 ? feature : null;
|
|
364
|
+
}
|
|
365
|
+
return feature;
|
|
366
|
+
}
|
|
367
|
+
function logSanitizeStats(stats, debugKey) {
|
|
368
|
+
if (process.env.NODE_ENV === "production") return;
|
|
369
|
+
if (stats.droppedFeatures === 0 && stats.droppedPointsFromMultiPoint === 0) return;
|
|
370
|
+
const resolvedKey = debugKey ?? "__default__";
|
|
371
|
+
if (warnedKeys.has(resolvedKey)) return;
|
|
372
|
+
warnedKeys.add(resolvedKey);
|
|
373
|
+
console.warn("[zenit-sdk] GeoJSON sanitization dropped invalid geometries.", {
|
|
374
|
+
layerId: debugKey,
|
|
375
|
+
...stats
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
function sanitizeGeoJson(input, debugKey) {
|
|
379
|
+
const featureCollection = asFeatureCollection(input);
|
|
380
|
+
const safeFeatures = [];
|
|
381
|
+
const stats = {
|
|
382
|
+
droppedFeatures: 0,
|
|
383
|
+
droppedPointsFromMultiPoint: 0
|
|
384
|
+
};
|
|
385
|
+
const rawFeatures = Array.isArray(featureCollection.features) ? featureCollection.features : [];
|
|
386
|
+
rawFeatures.forEach((rawFeature) => {
|
|
387
|
+
const feature = rawFeature;
|
|
388
|
+
const geometryType = feature?.geometry?.type;
|
|
389
|
+
const originalCoords = feature?.geometry?.coordinates;
|
|
390
|
+
const sanitized = sanitizeFeature(feature);
|
|
391
|
+
if (!sanitized) {
|
|
392
|
+
stats.droppedFeatures += 1;
|
|
393
|
+
if (geometryType === "MultiPoint" && Array.isArray(originalCoords)) {
|
|
394
|
+
stats.droppedPointsFromMultiPoint += originalCoords.length;
|
|
395
|
+
}
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (geometryType === "MultiPoint" && Array.isArray(originalCoords)) {
|
|
399
|
+
const safeCoords = sanitized.geometry?.coordinates;
|
|
400
|
+
const safeCount = Array.isArray(safeCoords) ? safeCoords.length : 0;
|
|
401
|
+
stats.droppedPointsFromMultiPoint += Math.max(0, originalCoords.length - safeCount);
|
|
402
|
+
}
|
|
403
|
+
safeFeatures.push(sanitized);
|
|
404
|
+
});
|
|
405
|
+
logSanitizeStats(stats, debugKey);
|
|
406
|
+
return {
|
|
407
|
+
...featureCollection,
|
|
408
|
+
type: "FeatureCollection",
|
|
409
|
+
features: safeFeatures
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// src/react/map/layer-geojson.tsx
|
|
301
414
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
302
415
|
var POINT_GEOMETRY_TYPES = /* @__PURE__ */ new Set(["Point", "MultiPoint"]);
|
|
416
|
+
var DEV_MODE = typeof process !== "undefined" && process.env.NODE_ENV !== "production";
|
|
303
417
|
function normalizeBboxFromData(data) {
|
|
304
418
|
const bboxCandidate = data.bbox;
|
|
305
419
|
if (!Array.isArray(bboxCandidate) || bboxCandidate.length < 4) return null;
|
|
@@ -332,6 +446,45 @@ function isNonPointGeometry(feature) {
|
|
|
332
446
|
const geometryType = getGeometryType(feature);
|
|
333
447
|
return geometryType !== null && !POINT_GEOMETRY_TYPES.has(geometryType);
|
|
334
448
|
}
|
|
449
|
+
function isValidLatLng(latlng) {
|
|
450
|
+
const candidate = latlng;
|
|
451
|
+
return Boolean(
|
|
452
|
+
candidate && Number.isFinite(candidate.lat) && Number.isFinite(candidate.lng)
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
function createInvisibleFallbackMarker(latlng) {
|
|
456
|
+
return import_leaflet.default.circleMarker(latlng ?? [0, 0], {
|
|
457
|
+
radius: 0,
|
|
458
|
+
opacity: 0,
|
|
459
|
+
fillOpacity: 0,
|
|
460
|
+
interactive: false
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
function createInvisibleFallbackClusterMarker(latlng) {
|
|
464
|
+
return import_leaflet.default.marker(latlng ?? [0, 0], {
|
|
465
|
+
opacity: 0,
|
|
466
|
+
interactive: false
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
function ensurePaneName(mapInstance, paneName, fallbackPaneName) {
|
|
470
|
+
if (!mapInstance) return fallbackPaneName;
|
|
471
|
+
const existingPane = mapInstance.getPane(paneName) ?? mapInstance.createPane(paneName);
|
|
472
|
+
if (!existingPane && DEV_MODE) {
|
|
473
|
+
console.warn("[LayerGeoJson] pane unavailable, using fallback", { paneName, fallbackPaneName });
|
|
474
|
+
}
|
|
475
|
+
return mapInstance.getPane(paneName) ? paneName : fallbackPaneName;
|
|
476
|
+
}
|
|
477
|
+
function createPointDivIcon(style, isMobile) {
|
|
478
|
+
const size = isMobile ? 18 : 14;
|
|
479
|
+
const backgroundColor = style.fillColor ?? style.color ?? "#3388ff";
|
|
480
|
+
const borderColor = style.color ?? style.fillColor ?? "#3388ff";
|
|
481
|
+
return import_leaflet.default.divIcon({
|
|
482
|
+
className: "zenit-point-marker",
|
|
483
|
+
html: `<div style="width:${size}px;height:${size}px;border-radius:50%;background:${backgroundColor};border:2px solid ${borderColor};"></div>`,
|
|
484
|
+
iconSize: [size, size],
|
|
485
|
+
iconAnchor: [size / 2, size / 2]
|
|
486
|
+
});
|
|
487
|
+
}
|
|
335
488
|
function buildFeatureCollection(features) {
|
|
336
489
|
return {
|
|
337
490
|
type: "FeatureCollection",
|
|
@@ -352,14 +505,15 @@ var LayerGeoJson = ({
|
|
|
352
505
|
onEachFeature,
|
|
353
506
|
onPolygonLabel
|
|
354
507
|
}) => {
|
|
355
|
-
const
|
|
356
|
-
const
|
|
357
|
-
const
|
|
508
|
+
const safeData = (0, import_react.useMemo)(() => sanitizeGeoJson(data, String(layerId)), [data, layerId]);
|
|
509
|
+
const features = (0, import_react.useMemo)(() => safeData.features ?? [], [safeData]);
|
|
510
|
+
const fillFeatures = (0, import_react.useMemo)(() => features.filter(isNonPointGeometry), [features]);
|
|
511
|
+
const pointFeatures = (0, import_react.useMemo)(() => features.filter(isPointGeometry), [features]);
|
|
358
512
|
const dataVersionRef = (0, import_react.useRef)(0);
|
|
359
513
|
const prevSignatureRef = (0, import_react.useRef)("");
|
|
360
514
|
const firstId = features.length > 0 ? String(features[0]?.id ?? "") : "";
|
|
361
515
|
const lastId = features.length > 0 ? String(features[features.length - 1]?.id ?? "") : "";
|
|
362
|
-
const bbox = normalizeBboxFromData(
|
|
516
|
+
const bbox = normalizeBboxFromData(safeData);
|
|
363
517
|
const idsSample = buildIdsSample(features);
|
|
364
518
|
const signature = bbox ? `${layerId}|${features.length}|${firstId}|${lastId}|${idsSample}|${bbox[0]}|${bbox[1]}|${bbox[2]}|${bbox[3]}` : `${layerId}|${features.length}|${firstId}|${lastId}|${idsSample}`;
|
|
365
519
|
const signatureToken = signature.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
@@ -367,25 +521,40 @@ var LayerGeoJson = ({
|
|
|
367
521
|
dataVersionRef.current += 1;
|
|
368
522
|
prevSignatureRef.current = signature;
|
|
369
523
|
}
|
|
370
|
-
const fillData = fillFeatures.length > 0 ? buildFeatureCollection(fillFeatures) : null;
|
|
371
|
-
const pointsData = pointFeatures.length > 0 ? buildFeatureCollection(pointFeatures) : null;
|
|
524
|
+
const fillData = (0, import_react.useMemo)(() => fillFeatures.length > 0 ? buildFeatureCollection(fillFeatures) : null, [fillFeatures]);
|
|
525
|
+
const pointsData = (0, import_react.useMemo)(() => pointFeatures.length > 0 ? buildFeatureCollection(pointFeatures) : null, [pointFeatures]);
|
|
372
526
|
const clusterLayerRef = (0, import_react.useRef)(null);
|
|
373
527
|
const canCluster = typeof import_leaflet.default.markerClusterGroup === "function";
|
|
528
|
+
const resolvedFillPane = (0, import_react.useMemo)(
|
|
529
|
+
() => ensurePaneName(mapInstance, fillPaneName, "overlayPane"),
|
|
530
|
+
[fillPaneName, mapInstance]
|
|
531
|
+
);
|
|
532
|
+
const resolvedPointsPane = (0, import_react.useMemo)(
|
|
533
|
+
() => ensurePaneName(mapInstance, pointsPaneName, "markerPane"),
|
|
534
|
+
[mapInstance, pointsPaneName]
|
|
535
|
+
);
|
|
374
536
|
(0, import_react.useEffect)(() => {
|
|
375
537
|
if (!mapInstance || !panesReady || !pointsData || !canCluster) return;
|
|
376
538
|
const markerClusterGroup = import_leaflet.default.markerClusterGroup;
|
|
377
|
-
const
|
|
539
|
+
const clusterPaneName = resolvedPointsPane;
|
|
540
|
+
const clusterLayer = clusterLayerRef.current ?? markerClusterGroup({ pane: clusterPaneName, clusterPane: clusterPaneName });
|
|
378
541
|
clusterLayerRef.current = clusterLayer;
|
|
379
542
|
if (!mapInstance.hasLayer(clusterLayer)) {
|
|
380
543
|
mapInstance.addLayer(clusterLayer);
|
|
381
544
|
}
|
|
382
545
|
clusterLayer.clearLayers();
|
|
383
546
|
const geoJsonLayer = import_leaflet.default.geoJSON(pointsData, {
|
|
384
|
-
pointToLayer: (feature, latlng) =>
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
547
|
+
pointToLayer: (feature, latlng) => {
|
|
548
|
+
if (!isValidLatLng(latlng)) {
|
|
549
|
+
return createInvisibleFallbackClusterMarker();
|
|
550
|
+
}
|
|
551
|
+
const style = styleFn(feature, layerType, baseOpacity);
|
|
552
|
+
return import_leaflet.default.marker(latlng, {
|
|
553
|
+
icon: createPointDivIcon(style, isMobile),
|
|
554
|
+
pane: clusterPaneName,
|
|
555
|
+
interactive: true
|
|
556
|
+
});
|
|
557
|
+
},
|
|
389
558
|
onEachFeature
|
|
390
559
|
});
|
|
391
560
|
clusterLayer.addLayer(geoJsonLayer);
|
|
@@ -404,7 +573,7 @@ var LayerGeoJson = ({
|
|
|
404
573
|
onEachFeature,
|
|
405
574
|
panesReady,
|
|
406
575
|
pointsData,
|
|
407
|
-
|
|
576
|
+
resolvedPointsPane,
|
|
408
577
|
styleFn
|
|
409
578
|
]);
|
|
410
579
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
@@ -412,7 +581,7 @@ var LayerGeoJson = ({
|
|
|
412
581
|
import_react_leaflet.GeoJSON,
|
|
413
582
|
{
|
|
414
583
|
data: fillData,
|
|
415
|
-
pane:
|
|
584
|
+
pane: resolvedFillPane,
|
|
416
585
|
style: (feature) => styleFn(feature, layerType, baseOpacity),
|
|
417
586
|
onEachFeature: (feature, layer) => {
|
|
418
587
|
onEachFeature(feature, layer);
|
|
@@ -425,11 +594,16 @@ var LayerGeoJson = ({
|
|
|
425
594
|
import_react_leaflet.GeoJSON,
|
|
426
595
|
{
|
|
427
596
|
data: pointsData,
|
|
428
|
-
pane:
|
|
429
|
-
pointToLayer: (feature, latlng) =>
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
597
|
+
pane: resolvedPointsPane,
|
|
598
|
+
pointToLayer: (feature, latlng) => {
|
|
599
|
+
if (!isValidLatLng(latlng)) {
|
|
600
|
+
return createInvisibleFallbackMarker();
|
|
601
|
+
}
|
|
602
|
+
return import_leaflet.default.circleMarker(latlng, {
|
|
603
|
+
radius: isMobile ? 8 : 6,
|
|
604
|
+
...styleFn(feature, layerType, baseOpacity)
|
|
605
|
+
});
|
|
606
|
+
},
|
|
433
607
|
onEachFeature
|
|
434
608
|
},
|
|
435
609
|
`points-${layerId}-${signatureToken}-v${dataVersionRef.current}`
|
|
@@ -511,7 +685,22 @@ function useGeolocation(options) {
|
|
|
511
685
|
var import_leaflet2 = __toESM(require("leaflet"));
|
|
512
686
|
var POPUP_STYLE_ID = "zenit-leaflet-popup-styles";
|
|
513
687
|
var POPUP_EXCLUDED_KEYS = /* @__PURE__ */ new Set(["geom", "geometry", "_private"]);
|
|
514
|
-
var
|
|
688
|
+
var POPUP_TITLE_KEYS = ["id", "nombre", "name", "title", "titulo", "cluster"];
|
|
689
|
+
var POPUP_BADGE_KEYS = ["tipo", "type", "category", "categoria", "estado", "status"];
|
|
690
|
+
var POPUP_DESCRIPTION_KEYS = ["descripcion", "description", "desc"];
|
|
691
|
+
var CURRENCY_KEYWORDS = [
|
|
692
|
+
"capital",
|
|
693
|
+
"monto",
|
|
694
|
+
"precio",
|
|
695
|
+
"costo",
|
|
696
|
+
"valor",
|
|
697
|
+
"ingreso",
|
|
698
|
+
"egreso",
|
|
699
|
+
"saldo",
|
|
700
|
+
"subtotal",
|
|
701
|
+
"venta",
|
|
702
|
+
"compra"
|
|
703
|
+
];
|
|
515
704
|
var DESKTOP_POPUP_DIMENSIONS = { maxWidth: 360, minWidth: 280, maxHeight: 520 };
|
|
516
705
|
var MOBILE_POPUP_DIMENSIONS = { maxWidth: 300, minWidth: 240, maxHeight: 420 };
|
|
517
706
|
var ZENIT_LEAFLET_POPUP_STYLES = `
|
|
@@ -629,6 +818,68 @@ function escapeHtml(value) {
|
|
|
629
818
|
function formatLabel(key) {
|
|
630
819
|
return key.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").trim();
|
|
631
820
|
}
|
|
821
|
+
function isHexColor(value) {
|
|
822
|
+
return /^#([0-9A-F]{3}){1,2}$/i.test(value.trim());
|
|
823
|
+
}
|
|
824
|
+
function isCurrencyField(key) {
|
|
825
|
+
const normalized = key.trim().toLowerCase();
|
|
826
|
+
return CURRENCY_KEYWORDS.some((keyword) => normalized.includes(keyword));
|
|
827
|
+
}
|
|
828
|
+
function formatCurrency(value) {
|
|
829
|
+
return new Intl.NumberFormat("es-GT", { style: "currency", currency: "GTQ" }).format(value);
|
|
830
|
+
}
|
|
831
|
+
function getBadgeForProperty(key, value) {
|
|
832
|
+
if (value === null || value === void 0) return null;
|
|
833
|
+
const normalizedKey = key.trim().toLowerCase();
|
|
834
|
+
const normalizedValue = String(value).trim().toLowerCase();
|
|
835
|
+
const byType = {
|
|
836
|
+
mora: { label: "Mora", bg: "#fee2e2", color: "#b91c1c", border: "#fecaca" },
|
|
837
|
+
castigo: { label: "Castigo", bg: "#ffedd5", color: "#c2410c", border: "#fed7aa" },
|
|
838
|
+
colocacion: { label: "Colocaci\xF3n", bg: "#dbeafe", color: "#1d4ed8", border: "#bfdbfe" },
|
|
839
|
+
sano: { label: "Sano", bg: "#dcfce7", color: "#15803d", border: "#bbf7d0" }
|
|
840
|
+
};
|
|
841
|
+
const byStatus = {
|
|
842
|
+
activo: { label: "Activo", bg: "#dcfce7", color: "#15803d", border: "#bbf7d0" },
|
|
843
|
+
inactivo: { label: "Inactivo", bg: "#fef9c3", color: "#a16207", border: "#fde68a" },
|
|
844
|
+
pendiente: { label: "Pendiente", bg: "#fef9c3", color: "#a16207", border: "#fde68a" },
|
|
845
|
+
cancelado: { label: "Cancelado", bg: "#fee2e2", color: "#b91c1c", border: "#fecaca" },
|
|
846
|
+
rechazado: { label: "Rechazado", bg: "#fee2e2", color: "#b91c1c", border: "#fecaca" }
|
|
847
|
+
};
|
|
848
|
+
if (["tipo", "type", "category", "categoria"].includes(normalizedKey)) {
|
|
849
|
+
return byType[normalizedValue] ?? {
|
|
850
|
+
label: String(value),
|
|
851
|
+
bg: "#e0e7ff",
|
|
852
|
+
color: "#4338ca",
|
|
853
|
+
border: "#c7d2fe"
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
if (["estado", "status"].includes(normalizedKey)) {
|
|
857
|
+
return byStatus[normalizedValue] ?? {
|
|
858
|
+
label: String(value),
|
|
859
|
+
bg: "#f1f5f9",
|
|
860
|
+
color: "#475569",
|
|
861
|
+
border: "#cbd5e1"
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
return null;
|
|
865
|
+
}
|
|
866
|
+
function findHeaderProperties(properties) {
|
|
867
|
+
const entries = Object.entries(properties);
|
|
868
|
+
const titleEntry = entries.find(
|
|
869
|
+
([key, value]) => POPUP_TITLE_KEYS.includes(key.trim().toLowerCase()) && String(value ?? "").trim().length > 0
|
|
870
|
+
);
|
|
871
|
+
const badgeEntry = entries.find(
|
|
872
|
+
([key, value]) => POPUP_BADGE_KEYS.includes(key.trim().toLowerCase()) && String(value ?? "").trim().length > 0
|
|
873
|
+
);
|
|
874
|
+
const descriptionEntry = entries.find(
|
|
875
|
+
([key, value]) => POPUP_DESCRIPTION_KEYS.includes(key.trim().toLowerCase()) && String(value ?? "").trim().length > 0
|
|
876
|
+
);
|
|
877
|
+
return {
|
|
878
|
+
title: titleEntry ? { key: titleEntry[0], value: String(titleEntry[1]).trim() } : void 0,
|
|
879
|
+
badge: badgeEntry ? { key: badgeEntry[0], value: badgeEntry[1] } : void 0,
|
|
880
|
+
description: descriptionEntry ? { key: descriptionEntry[0], value: String(descriptionEntry[1]).trim() } : void 0
|
|
881
|
+
};
|
|
882
|
+
}
|
|
632
883
|
function renderPopupValue(value) {
|
|
633
884
|
if (value === null || value === void 0) return '<span style="color:#94a3b8;">Sin datos</span>';
|
|
634
885
|
if (value instanceof Date) {
|
|
@@ -654,12 +905,8 @@ function renderPopupValue(value) {
|
|
|
654
905
|
return escapeHtml(String(value));
|
|
655
906
|
}
|
|
656
907
|
function extractPopupHeader(properties) {
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
const normalized = key.trim().toLowerCase();
|
|
660
|
-
return POPUP_HEADER_KEYS.includes(normalized) && value.trim().length > 0;
|
|
661
|
-
});
|
|
662
|
-
return entry ? entry[1].trim() : null;
|
|
908
|
+
const header = findHeaderProperties(properties);
|
|
909
|
+
return header.title?.value ?? null;
|
|
663
910
|
}
|
|
664
911
|
function shouldIncludePopupEntry(key, value) {
|
|
665
912
|
if (!key) return false;
|
|
@@ -672,21 +919,36 @@ function shouldIncludePopupEntry(key, value) {
|
|
|
672
919
|
return true;
|
|
673
920
|
}
|
|
674
921
|
function createPopupContent(properties) {
|
|
675
|
-
const
|
|
922
|
+
const header = findHeaderProperties(properties);
|
|
923
|
+
const headerText = header.title?.value ?? extractPopupHeader(properties);
|
|
924
|
+
const usedKeys = new Set([header.title?.key, header.badge?.key, header.description?.key].filter(Boolean));
|
|
925
|
+
const badge = header.badge ? getBadgeForProperty(header.badge.key, header.badge.value) : null;
|
|
926
|
+
const colorValue = properties.color;
|
|
927
|
+
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>` : "";
|
|
676
928
|
const entries = Object.entries(properties).filter(([key, value]) => {
|
|
677
929
|
if (!shouldIncludePopupEntry(key, value)) return false;
|
|
678
|
-
if (
|
|
930
|
+
if (usedKeys.has(key)) return false;
|
|
679
931
|
return true;
|
|
680
932
|
});
|
|
681
933
|
if (entries.length === 0) {
|
|
682
934
|
return '<div style="padding:8px 0; color:#64748b; text-align:center;">Sin datos disponibles</div>';
|
|
683
935
|
}
|
|
684
|
-
const headerHtml = headerText ? `<div style="
|
|
685
|
-
|
|
936
|
+
const headerHtml = headerText || badge ? `<div style="display:flex; justify-content:space-between; gap:8px; align-items:flex-start; margin-bottom:8px;">
|
|
937
|
+
<div style="font-weight:700; font-size:14px; color:#0f172a;">${escapeHtml(headerText ?? "Detalle")}</div>
|
|
938
|
+
${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(
|
|
939
|
+
badge.label
|
|
940
|
+
)}</span>` : ""}
|
|
941
|
+
</div>` : "";
|
|
942
|
+
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(
|
|
943
|
+
header.description.value
|
|
686
944
|
)}</div>` : "";
|
|
687
945
|
const rowsHtml = entries.map(([key, value]) => {
|
|
688
946
|
const label = escapeHtml(formatLabel(key));
|
|
689
|
-
const
|
|
947
|
+
const normalizedKey = key.trim().toLowerCase();
|
|
948
|
+
let valueHtml = renderPopupValue(value);
|
|
949
|
+
if (typeof value === "number") {
|
|
950
|
+
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>`;
|
|
951
|
+
}
|
|
690
952
|
return `
|
|
691
953
|
<div style="display:grid; grid-template-columns:minmax(90px, 35%) 1fr; gap:8px; padding:6px 0; border-bottom:1px solid #e2e8f0;">
|
|
692
954
|
<div style="font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:0.04em; color:#64748b;">${label}</div>
|
|
@@ -694,7 +956,7 @@ function createPopupContent(properties) {
|
|
|
694
956
|
</div>
|
|
695
957
|
`;
|
|
696
958
|
}).join("");
|
|
697
|
-
return `<div>${headerHtml}${rowsHtml}</div>`;
|
|
959
|
+
return `<div>${colorBar}${headerHtml}${descriptionHtml}${rowsHtml}</div>`;
|
|
698
960
|
}
|
|
699
961
|
function isPolygonType(layerType, geometryType) {
|
|
700
962
|
const candidate = (layerType ?? geometryType ?? "").toLowerCase();
|
|
@@ -704,21 +966,27 @@ function calculateZoomBasedOpacity(zoom, baseOpacity, layerType, geometryType) {
|
|
|
704
966
|
if (!isPolygonType(layerType, geometryType)) return clampOpacity4(baseOpacity);
|
|
705
967
|
const minZoom = 11;
|
|
706
968
|
const maxZoom = 15;
|
|
707
|
-
const minFactor = 0.
|
|
969
|
+
const minFactor = 0.5;
|
|
708
970
|
if (maxZoom <= minZoom) return clampOpacity4(baseOpacity * minFactor);
|
|
709
971
|
const t = clampNumber2((zoom - minZoom) / (maxZoom - minZoom), 0, 1);
|
|
710
972
|
const factor = 1 - (1 - minFactor) * t;
|
|
711
973
|
return clampOpacity4(baseOpacity * factor);
|
|
712
974
|
}
|
|
713
975
|
function layerStyleToLeaflet(options) {
|
|
714
|
-
const { baseOpacity, zoom, layerStyle, geometryType, layerType } = options;
|
|
976
|
+
const { baseOpacity, zoom, layerStyle, geometryType, layerType, properties } = options;
|
|
715
977
|
const sanitizedOpacity = clampOpacity4(baseOpacity);
|
|
716
978
|
const zoomOpacity = calculateZoomBasedOpacity(zoom, sanitizedOpacity, layerType, geometryType);
|
|
717
|
-
const
|
|
979
|
+
const badgeForType = getBadgeForProperty("type", properties?.type ?? properties?.tipo ?? properties?.category ?? properties?.categoria);
|
|
980
|
+
const badgeForStatus = getBadgeForProperty("status", properties?.status ?? properties?.estado);
|
|
981
|
+
const featureColor = (typeof properties?.color === "string" && properties.color.trim().length > 0 ? properties.color : void 0) ?? badgeForType?.color ?? badgeForStatus?.color;
|
|
982
|
+
const styleFillOpacity = typeof layerStyle?.fillOpacity === "number" ? clampOpacity4(layerStyle.fillOpacity) : 0.65;
|
|
983
|
+
const fallbackColor = "#2563eb";
|
|
984
|
+
const resolvedColor = featureColor ?? layerStyle?.color ?? layerStyle?.fillColor ?? fallbackColor;
|
|
985
|
+
const resolvedFillColor = featureColor ?? layerStyle?.fillColor ?? layerStyle?.color ?? fallbackColor;
|
|
718
986
|
return {
|
|
719
|
-
color:
|
|
987
|
+
color: resolvedColor,
|
|
720
988
|
weight: layerStyle?.weight ?? 2,
|
|
721
|
-
fillColor:
|
|
989
|
+
fillColor: resolvedFillColor,
|
|
722
990
|
opacity: clampOpacity4(Math.max(0.35, zoomOpacity * 0.9)),
|
|
723
991
|
fillOpacity: clampOpacity4(zoomOpacity * styleFillOpacity)
|
|
724
992
|
};
|
|
@@ -1116,6 +1384,7 @@ var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
|
1116
1384
|
var DEFAULT_CENTER = [0, 0];
|
|
1117
1385
|
var DEFAULT_ZOOM = 3;
|
|
1118
1386
|
var LABELS_PANE_NAME = "zenit-labels-pane";
|
|
1387
|
+
var DEV_MODE2 = typeof process !== "undefined" && process.env.NODE_ENV !== "production";
|
|
1119
1388
|
function isRecord(value) {
|
|
1120
1389
|
return typeof value === "object" && value !== null;
|
|
1121
1390
|
}
|
|
@@ -1139,6 +1408,8 @@ function getFeatureLayerId(feature) {
|
|
|
1139
1408
|
return layerId;
|
|
1140
1409
|
}
|
|
1141
1410
|
var DESCRIPTION_KEYS = /* @__PURE__ */ new Set(["descripcion", "description"]);
|
|
1411
|
+
var POINT_GEOMETRY_TYPES2 = /* @__PURE__ */ new Set(["Point", "MultiPoint"]);
|
|
1412
|
+
var POLYGON_GEOMETRY_TYPES = /* @__PURE__ */ new Set(["Polygon", "MultiPolygon"]);
|
|
1142
1413
|
function normalizeDescriptionValue(value) {
|
|
1143
1414
|
if (value === void 0 || value === null) return null;
|
|
1144
1415
|
if (typeof value === "string") {
|
|
@@ -1158,6 +1429,31 @@ function extractDescriptionValue(properties) {
|
|
|
1158
1429
|
if (!matches) return null;
|
|
1159
1430
|
return normalizeDescriptionValue(matches[1]);
|
|
1160
1431
|
}
|
|
1432
|
+
function normalizeLayerId(value) {
|
|
1433
|
+
if (value === null || value === void 0) return null;
|
|
1434
|
+
const normalized = String(value).trim();
|
|
1435
|
+
return normalized ? normalized : null;
|
|
1436
|
+
}
|
|
1437
|
+
function isLayerIdMatch(a, b) {
|
|
1438
|
+
const normalizedA = normalizeLayerId(a);
|
|
1439
|
+
const normalizedB = normalizeLayerId(b);
|
|
1440
|
+
return normalizedA !== null && normalizedB !== null && normalizedA === normalizedB;
|
|
1441
|
+
}
|
|
1442
|
+
function getClickIntent(params) {
|
|
1443
|
+
const geometryType = params.feature?.geometry?.type;
|
|
1444
|
+
if (geometryType && POINT_GEOMETRY_TYPES2.has(geometryType)) return "point";
|
|
1445
|
+
if (params.leafletLayer instanceof import_leaflet4.default.Marker) return "point";
|
|
1446
|
+
if (geometryType && POLYGON_GEOMETRY_TYPES.has(geometryType)) return "polygon";
|
|
1447
|
+
if (params.leafletLayer instanceof import_leaflet4.default.Path) return "polygon";
|
|
1448
|
+
return "unknown";
|
|
1449
|
+
}
|
|
1450
|
+
function candidateLayerId(candidate) {
|
|
1451
|
+
return getFeatureLayerId(candidate);
|
|
1452
|
+
}
|
|
1453
|
+
function isCandidateGeometryType(candidate, allowedTypes) {
|
|
1454
|
+
const geometryType = candidate?.geometry?.type;
|
|
1455
|
+
return Boolean(geometryType && allowedTypes.has(geometryType));
|
|
1456
|
+
}
|
|
1161
1457
|
function getFeatureStyleOverrides(feature) {
|
|
1162
1458
|
const candidate = feature?.properties?._style;
|
|
1163
1459
|
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return null;
|
|
@@ -1175,17 +1471,50 @@ function buildFeaturePopupHtml(feature) {
|
|
|
1175
1471
|
const rendered = createPopupContent(properties);
|
|
1176
1472
|
return rendered ? rendered : null;
|
|
1177
1473
|
}
|
|
1178
|
-
function pickIntersectFeature(
|
|
1474
|
+
function pickIntersectFeature(params) {
|
|
1475
|
+
const { baseFeature, candidates, expectedLayerId, clickIntent } = params;
|
|
1179
1476
|
if (!Array.isArray(candidates) || candidates.length === 0) return null;
|
|
1477
|
+
const pickByGeometry = (pool, intent) => {
|
|
1478
|
+
const preferredTypes = intent === "point" ? POINT_GEOMETRY_TYPES2 : intent === "polygon" ? POLYGON_GEOMETRY_TYPES : null;
|
|
1479
|
+
if (!preferredTypes) return null;
|
|
1480
|
+
const found = pool.find((candidate) => isCandidateGeometryType(candidate, preferredTypes));
|
|
1481
|
+
if (!found) return null;
|
|
1482
|
+
return { feature: found, reason: `geometry:${intent}` };
|
|
1483
|
+
};
|
|
1484
|
+
const getResult = (feature, reason) => ({
|
|
1485
|
+
feature,
|
|
1486
|
+
selectedIdx: candidates.findIndex((candidate) => candidate === feature),
|
|
1487
|
+
reason
|
|
1488
|
+
});
|
|
1489
|
+
const sameLayer = candidates.filter(
|
|
1490
|
+
(candidate) => isLayerIdMatch(candidateLayerId(candidate), expectedLayerId)
|
|
1491
|
+
);
|
|
1492
|
+
const geometryFromSameLayer = pickByGeometry(sameLayer, clickIntent);
|
|
1493
|
+
if (geometryFromSameLayer?.feature) {
|
|
1494
|
+
return getResult(geometryFromSameLayer.feature, `sameLayer>${geometryFromSameLayer.reason}`);
|
|
1495
|
+
}
|
|
1496
|
+
if (sameLayer.length > 0) {
|
|
1497
|
+
const sameLayerWithDescription = sameLayer.find(
|
|
1498
|
+
(candidate) => extractDescriptionValue(candidate?.properties)
|
|
1499
|
+
);
|
|
1500
|
+
if (sameLayerWithDescription) {
|
|
1501
|
+
return getResult(sameLayerWithDescription, "sameLayer>description");
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
const geometryFallback = pickByGeometry(candidates, clickIntent);
|
|
1505
|
+
if (geometryFallback?.feature) {
|
|
1506
|
+
return getResult(geometryFallback.feature, `anyLayer>${geometryFallback.reason}`);
|
|
1507
|
+
}
|
|
1180
1508
|
const baseId = baseFeature?.id;
|
|
1181
1509
|
if (baseId !== void 0 && baseId !== null) {
|
|
1182
1510
|
const matchById = candidates.find((candidate) => candidate?.id === baseId);
|
|
1183
|
-
if (matchById) return matchById;
|
|
1511
|
+
if (matchById) return getResult(matchById, "fallback>baseId");
|
|
1184
1512
|
}
|
|
1185
1513
|
const matchWithDescription = candidates.find(
|
|
1186
1514
|
(candidate) => extractDescriptionValue(candidate?.properties)
|
|
1187
1515
|
);
|
|
1188
|
-
return matchWithDescription
|
|
1516
|
+
if (matchWithDescription) return getResult(matchWithDescription, "fallback>description");
|
|
1517
|
+
return getResult(candidates[0], "fallback>first");
|
|
1189
1518
|
}
|
|
1190
1519
|
function normalizeCenterTuple(center) {
|
|
1191
1520
|
if (!center) return null;
|
|
@@ -1592,7 +1921,8 @@ var ZenitMap = (0, import_react5.forwardRef)(({
|
|
|
1592
1921
|
}, [currentZoom, decoratedLayers, labelKeyIndex, layerMetaIndex, resolveLayerStyle]);
|
|
1593
1922
|
const ensureLayerPanes = (0, import_react5.useCallback)(
|
|
1594
1923
|
(targetMap, targetLayers) => {
|
|
1595
|
-
const
|
|
1924
|
+
const fillBaseZIndex = 400;
|
|
1925
|
+
const pointsBaseZIndex = 700;
|
|
1596
1926
|
targetLayers.forEach((layer) => {
|
|
1597
1927
|
const order = Number.isFinite(layer.displayOrder) ? layer.displayOrder : 0;
|
|
1598
1928
|
const orderOffset = Math.max(0, Math.min(order, 150));
|
|
@@ -1600,8 +1930,8 @@ var ZenitMap = (0, import_react5.forwardRef)(({
|
|
|
1600
1930
|
const pointPaneName = `zenit-layer-${layer.layerId}-points`;
|
|
1601
1931
|
const fillPane = targetMap.getPane(fillPaneName) ?? targetMap.createPane(fillPaneName);
|
|
1602
1932
|
const pointPane = targetMap.getPane(pointPaneName) ?? targetMap.createPane(pointPaneName);
|
|
1603
|
-
fillPane.style.zIndex = String(
|
|
1604
|
-
pointPane.style.zIndex = String(
|
|
1933
|
+
fillPane.style.zIndex = String(fillBaseZIndex + orderOffset);
|
|
1934
|
+
pointPane.style.zIndex = String(pointsBaseZIndex + orderOffset);
|
|
1605
1935
|
});
|
|
1606
1936
|
},
|
|
1607
1937
|
[]
|
|
@@ -1619,114 +1949,149 @@ var ZenitMap = (0, import_react5.forwardRef)(({
|
|
|
1619
1949
|
setPanesReady(false);
|
|
1620
1950
|
return;
|
|
1621
1951
|
}
|
|
1622
|
-
if (orderedLayers.length === 0) {
|
|
1952
|
+
if (orderedLayers.length === 0 && !overlayGeojson) {
|
|
1623
1953
|
return;
|
|
1624
1954
|
}
|
|
1625
1955
|
const layerTargets = orderedLayers.map((layer) => ({
|
|
1626
1956
|
layerId: layer.mapLayer.layerId,
|
|
1627
1957
|
displayOrder: layer.displayOrder
|
|
1628
1958
|
}));
|
|
1959
|
+
if (overlayGeojson) {
|
|
1960
|
+
layerTargets.push({ layerId: "overlay-geojson", displayOrder: 999 });
|
|
1961
|
+
}
|
|
1629
1962
|
ensureLayerPanes(mapInstance, layerTargets);
|
|
1630
1963
|
const first = layerTargets[0];
|
|
1631
|
-
const testPane = mapInstance.getPane(`zenit-layer-${first.layerId}-fill`);
|
|
1964
|
+
const testPane = first ? mapInstance.getPane(`zenit-layer-${first.layerId}-fill`) : null;
|
|
1632
1965
|
const labelsPane = mapInstance.getPane(LABELS_PANE_NAME);
|
|
1633
1966
|
if (testPane && labelsPane) {
|
|
1634
1967
|
setPanesReady(true);
|
|
1635
1968
|
}
|
|
1636
|
-
}, [mapInstance, orderedLayers, ensureLayerPanes]);
|
|
1637
|
-
const overlayOnEachFeature = (0, import_react5.
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
if (isPointFeature && layer.bindTooltip) {
|
|
1666
|
-
layer.bindTooltip("Click para ver detalle", {
|
|
1667
|
-
sticky: true,
|
|
1668
|
-
direction: "top",
|
|
1669
|
-
opacity: 0.9,
|
|
1670
|
-
className: "zenit-map-tooltip"
|
|
1969
|
+
}, [mapInstance, orderedLayers, overlayGeojson, ensureLayerPanes]);
|
|
1970
|
+
const overlayOnEachFeature = (0, import_react5.useCallback)((feature, layer) => {
|
|
1971
|
+
const layerId = getFeatureLayerId(feature) ?? void 0;
|
|
1972
|
+
const geometryType = feature?.geometry?.type;
|
|
1973
|
+
const clickIntent = getClickIntent({ feature, leafletLayer: layer });
|
|
1974
|
+
const isPointFeature = clickIntent === "point";
|
|
1975
|
+
const originalStyle = layer instanceof import_leaflet4.default.Path ? {
|
|
1976
|
+
color: layer.options.color,
|
|
1977
|
+
weight: layer.options.weight,
|
|
1978
|
+
fillColor: layer.options.fillColor,
|
|
1979
|
+
opacity: layer.options.opacity,
|
|
1980
|
+
fillOpacity: layer.options.fillOpacity
|
|
1981
|
+
} : null;
|
|
1982
|
+
const originalRadius = layer instanceof import_leaflet4.default.CircleMarker ? layer.getRadius() : null;
|
|
1983
|
+
if (featureInfoMode === "popup") {
|
|
1984
|
+
const content = buildFeaturePopupHtml(feature);
|
|
1985
|
+
if (content) {
|
|
1986
|
+
const { maxWidth, minWidth, maxHeight } = getPopupDimensions();
|
|
1987
|
+
layer.bindPopup(content, {
|
|
1988
|
+
maxWidth,
|
|
1989
|
+
minWidth,
|
|
1990
|
+
maxHeight,
|
|
1991
|
+
className: "custom-leaflet-popup",
|
|
1992
|
+
autoClose: true,
|
|
1993
|
+
closeOnClick: true,
|
|
1994
|
+
autoPan: true,
|
|
1995
|
+
closeButton: true,
|
|
1996
|
+
keepInView: true,
|
|
1997
|
+
autoPanPadding: [50, 50]
|
|
1671
1998
|
});
|
|
1672
1999
|
}
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
geometry: feature.geometry
|
|
1681
|
-
}).then((response) => {
|
|
1682
|
-
const geo = extractGeoJsonFeatureCollection(response);
|
|
1683
|
-
const candidates = geo?.features ?? [];
|
|
1684
|
-
const resolved = pickIntersectFeature(feature, candidates);
|
|
1685
|
-
if (!resolved?.properties) return;
|
|
1686
|
-
const mergedProperties = {
|
|
1687
|
-
...trackedFeature.properties ?? {},
|
|
1688
|
-
...resolved.properties
|
|
1689
|
-
};
|
|
1690
|
-
trackedFeature.properties = mergedProperties;
|
|
1691
|
-
const updatedHtml = buildFeaturePopupHtml({
|
|
1692
|
-
...feature,
|
|
1693
|
-
properties: mergedProperties
|
|
1694
|
-
});
|
|
1695
|
-
if (updatedHtml && layer.setPopupContent) {
|
|
1696
|
-
layer.setPopupContent(updatedHtml);
|
|
1697
|
-
}
|
|
1698
|
-
}).catch(() => {
|
|
1699
|
-
trackedFeature.__zenit_popup_loaded = false;
|
|
1700
|
-
});
|
|
1701
|
-
}
|
|
1702
|
-
}
|
|
1703
|
-
onFeatureClick?.(feature, layerId);
|
|
2000
|
+
}
|
|
2001
|
+
if (isPointFeature && layer.bindTooltip) {
|
|
2002
|
+
layer.bindTooltip("Click para ver detalle", {
|
|
2003
|
+
sticky: true,
|
|
2004
|
+
direction: "top",
|
|
2005
|
+
opacity: 0.9,
|
|
2006
|
+
className: "zenit-map-tooltip"
|
|
1704
2007
|
});
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
2008
|
+
}
|
|
2009
|
+
layer.on("click", () => {
|
|
2010
|
+
if (featureInfoMode === "popup" && client && layerId !== void 0 && !extractDescriptionValue(feature?.properties) && feature?.geometry) {
|
|
2011
|
+
if (DEV_MODE2) {
|
|
2012
|
+
console.debug("[ZenitMap] click/intersect:start", {
|
|
2013
|
+
expectedLayerId: layerId,
|
|
2014
|
+
geometryType,
|
|
2015
|
+
clickIntent,
|
|
2016
|
+
leafletLayerType: layer?.constructor?.name,
|
|
2017
|
+
pane: layer?.options?.pane
|
|
1712
2018
|
});
|
|
1713
2019
|
}
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
2020
|
+
const trackedFeature = feature;
|
|
2021
|
+
if (!trackedFeature.__zenit_popup_loaded) {
|
|
2022
|
+
trackedFeature.__zenit_popup_loaded = true;
|
|
2023
|
+
client.layers.getLayerGeoJsonIntersect({
|
|
2024
|
+
id: layerId,
|
|
2025
|
+
geometry: feature.geometry
|
|
2026
|
+
}).then((response) => {
|
|
2027
|
+
const geo = extractGeoJsonFeatureCollection(response);
|
|
2028
|
+
const candidates = geo?.features ?? [];
|
|
2029
|
+
const selection = pickIntersectFeature({
|
|
2030
|
+
baseFeature: feature,
|
|
2031
|
+
candidates,
|
|
2032
|
+
expectedLayerId: layerId,
|
|
2033
|
+
clickIntent
|
|
2034
|
+
});
|
|
2035
|
+
const resolved = selection?.feature;
|
|
2036
|
+
if (DEV_MODE2) {
|
|
2037
|
+
console.debug("[ZenitMap] click/intersect:result", {
|
|
2038
|
+
expectedLayerId: layerId,
|
|
2039
|
+
clickIntent,
|
|
2040
|
+
candidatesCount: candidates.length,
|
|
2041
|
+
candidates: candidates.map((candidate, idx) => ({
|
|
2042
|
+
idx,
|
|
2043
|
+
geomType: candidate?.geometry?.type,
|
|
2044
|
+
candidateLayerId: candidateLayerId(candidate),
|
|
2045
|
+
hasDescription: Boolean(extractDescriptionValue(candidate?.properties))
|
|
2046
|
+
})),
|
|
2047
|
+
selectedIdx: selection?.selectedIdx ?? -1,
|
|
2048
|
+
selectionRule: selection?.reason ?? "none"
|
|
2049
|
+
});
|
|
2050
|
+
}
|
|
2051
|
+
if (!resolved?.properties) return;
|
|
2052
|
+
const mergedProperties = {
|
|
2053
|
+
...trackedFeature.properties ?? {},
|
|
2054
|
+
...resolved.properties
|
|
2055
|
+
};
|
|
2056
|
+
trackedFeature.properties = mergedProperties;
|
|
2057
|
+
const updatedHtml = buildFeaturePopupHtml({
|
|
2058
|
+
...feature,
|
|
2059
|
+
properties: mergedProperties
|
|
2060
|
+
});
|
|
2061
|
+
if (updatedHtml && layer.setPopupContent) {
|
|
2062
|
+
layer.setPopupContent(updatedHtml);
|
|
2063
|
+
}
|
|
2064
|
+
}).catch(() => {
|
|
2065
|
+
trackedFeature.__zenit_popup_loaded = false;
|
|
2066
|
+
});
|
|
1725
2067
|
}
|
|
1726
|
-
}
|
|
1727
|
-
|
|
2068
|
+
}
|
|
2069
|
+
onFeatureClick?.(feature, layerId);
|
|
2070
|
+
});
|
|
2071
|
+
layer.on("mouseover", () => {
|
|
2072
|
+
if (layer instanceof import_leaflet4.default.Path && originalStyle) {
|
|
2073
|
+
layer.setStyle({
|
|
2074
|
+
...originalStyle,
|
|
2075
|
+
weight: (originalStyle.weight ?? 2) + 1,
|
|
2076
|
+
opacity: Math.min(1, (originalStyle.opacity ?? 1) + 0.2),
|
|
2077
|
+
fillOpacity: Math.min(1, (originalStyle.fillOpacity ?? 0.8) + 0.1)
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
if (layer instanceof import_leaflet4.default.CircleMarker && typeof originalRadius === "number") {
|
|
2081
|
+
layer.setRadius(originalRadius + 1);
|
|
2082
|
+
}
|
|
2083
|
+
onFeatureHover?.(feature, layerId);
|
|
2084
|
+
});
|
|
2085
|
+
layer.on("mouseout", () => {
|
|
2086
|
+
if (layer instanceof import_leaflet4.default.Path && originalStyle) {
|
|
2087
|
+
layer.setStyle(originalStyle);
|
|
2088
|
+
}
|
|
2089
|
+
if (layer instanceof import_leaflet4.default.CircleMarker && typeof originalRadius === "number") {
|
|
2090
|
+
layer.setRadius(originalRadius);
|
|
2091
|
+
}
|
|
2092
|
+
});
|
|
1728
2093
|
}, [client, featureInfoMode, onFeatureClick, onFeatureHover]);
|
|
1729
|
-
const buildLayerStyle = (layerId, baseOpacity, feature, layerType) => {
|
|
2094
|
+
const buildLayerStyle = (0, import_react5.useCallback)((layerId, baseOpacity, feature, layerType) => {
|
|
1730
2095
|
const style = resolveLayerStyle(layerId);
|
|
1731
2096
|
const featureStyleOverrides = getFeatureStyleOverrides(feature);
|
|
1732
2097
|
const resolvedStyle = featureStyleOverrides ? { ...style ?? {}, ...featureStyleOverrides } : style;
|
|
@@ -1737,14 +2102,15 @@ var ZenitMap = (0, import_react5.forwardRef)(({
|
|
|
1737
2102
|
zoom: currentZoom,
|
|
1738
2103
|
layerStyle: resolvedStyle,
|
|
1739
2104
|
geometryType,
|
|
1740
|
-
layerType: resolvedLayerType
|
|
2105
|
+
layerType: resolvedLayerType,
|
|
2106
|
+
properties: feature?.properties ?? void 0
|
|
1741
2107
|
});
|
|
1742
|
-
};
|
|
1743
|
-
const makeStyleFnForLayer = (layerId) => {
|
|
2108
|
+
}, [currentZoom, resolveLayerStyle]);
|
|
2109
|
+
const makeStyleFnForLayer = (0, import_react5.useCallback)((layerId) => {
|
|
1744
2110
|
return (feature, layerType, baseOpacity) => {
|
|
1745
2111
|
return buildLayerStyle(layerId, baseOpacity ?? 1, feature, layerType);
|
|
1746
2112
|
};
|
|
1747
|
-
};
|
|
2113
|
+
}, [buildLayerStyle]);
|
|
1748
2114
|
(0, import_react5.useImperativeHandle)(ref, () => ({
|
|
1749
2115
|
setLayerOpacity: (layerId, opacity) => {
|
|
1750
2116
|
upsertUiOverride(layerId, { overrideOpacity: opacity });
|
|
@@ -1899,7 +2265,7 @@ var ZenitMap = (0, import_react5.forwardRef)(({
|
|
|
1899
2265
|
const layerType = layerState.layer?.layerType ?? layerState.mapLayer.layerType ?? void 0;
|
|
1900
2266
|
const labelKey = labelKeyIndex.get(String(layerState.mapLayer.layerId));
|
|
1901
2267
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react5.default.Fragment, { children: [
|
|
1902
|
-
layerState.data && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2268
|
+
layerState.data && panesReady && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1903
2269
|
LayerGeoJson,
|
|
1904
2270
|
{
|
|
1905
2271
|
layerId: layerState.mapLayer.layerId,
|
|
@@ -1942,7 +2308,7 @@ var ZenitMap = (0, import_react5.forwardRef)(({
|
|
|
1942
2308
|
)) : null
|
|
1943
2309
|
] }, layerState.mapLayer.layerId.toString());
|
|
1944
2310
|
}),
|
|
1945
|
-
overlayGeojson && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2311
|
+
overlayGeojson && panesReady && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1946
2312
|
LayerGeoJson,
|
|
1947
2313
|
{
|
|
1948
2314
|
layerId: "overlay-geojson",
|
|
@@ -1976,7 +2342,7 @@ var ZenitMap = (0, import_react5.forwardRef)(({
|
|
|
1976
2342
|
overflowY: "auto"
|
|
1977
2343
|
},
|
|
1978
2344
|
children: [
|
|
1979
|
-
overlayGeojson && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
2345
|
+
overlayGeojson && panesReady && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1980
2346
|
"div",
|
|
1981
2347
|
{
|
|
1982
2348
|
style: {
|
|
@@ -2664,6 +3030,14 @@ var ZenitLayerManager = ({
|
|
|
2664
3030
|
return String(a.mapLayer.layerId).localeCompare(String(b.mapLayer.layerId));
|
|
2665
3031
|
});
|
|
2666
3032
|
}, [effectiveStates, layers, resolveFeatureCount]);
|
|
3033
|
+
const hasPrefilters = (0, import_react7.useMemo)(() => {
|
|
3034
|
+
const candidates = [...mapLayers ?? [], ...map?.mapLayers ?? []];
|
|
3035
|
+
return candidates.some((layer) => {
|
|
3036
|
+
const record = layer;
|
|
3037
|
+
const applied = record.appliedFilters ?? record.prefilters ?? record.initialFilters ?? record.filters ?? (record.layerConfig?.appliedFilters ?? record.layerConfig?.prefilters);
|
|
3038
|
+
return !!applied && typeof applied === "object" && Object.keys(applied).length > 0;
|
|
3039
|
+
});
|
|
3040
|
+
}, [map?.mapLayers, mapLayers]);
|
|
2667
3041
|
const filterableLayers = (0, import_react7.useMemo)(() => {
|
|
2668
3042
|
return decoratedLayers.filter((entry) => {
|
|
2669
3043
|
const prefilters = entry.mapLayer.layerConfig?.prefilters;
|
|
@@ -2736,6 +3110,11 @@ var ZenitLayerManager = ({
|
|
|
2736
3110
|
setSelectedFilterValue("");
|
|
2737
3111
|
}
|
|
2738
3112
|
}, [filterFields, selectedFilterField]);
|
|
3113
|
+
(0, import_react7.useEffect)(() => {
|
|
3114
|
+
if (hasPrefilters && activeTab === "filters") {
|
|
3115
|
+
setActiveTab("layers");
|
|
3116
|
+
}
|
|
3117
|
+
}, [activeTab, hasPrefilters]);
|
|
2739
3118
|
(0, import_react7.useEffect)(() => {
|
|
2740
3119
|
if (activeTab !== "filters") return;
|
|
2741
3120
|
if (!selectedFilterLayer || !selectedFilterField || !activeCatalogKey) return;
|
|
@@ -3136,7 +3515,7 @@ var ZenitLayerManager = ({
|
|
|
3136
3515
|
]
|
|
3137
3516
|
}
|
|
3138
3517
|
),
|
|
3139
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
3518
|
+
!hasPrefilters && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
3140
3519
|
"button",
|
|
3141
3520
|
{
|
|
3142
3521
|
type: "button",
|
|
@@ -3153,8 +3532,9 @@ var ZenitLayerManager = ({
|
|
|
3153
3532
|
)
|
|
3154
3533
|
] }),
|
|
3155
3534
|
panelVisible && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { padding: "12px 10px 18px", overflowY: "auto", flex: 1, minHeight: 0 }, children: [
|
|
3535
|
+
hasPrefilters && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "zlm-badge", style: { marginBottom: 10 }, children: "Este mapa ya incluye filtros preaplicados" }),
|
|
3156
3536
|
activeTab === "layers" && renderLayerCards(),
|
|
3157
|
-
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: [
|
|
3537
|
+
!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: [
|
|
3158
3538
|
filterableLayers.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { style: { display: "flex", flexDirection: "column", gap: 6, fontSize: 12, color: "#475569" }, children: [
|
|
3159
3539
|
"Capa",
|
|
3160
3540
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|