zenit-sdk 0.2.0 → 0.2.2

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.
@@ -414,6 +414,28 @@ function sanitizeGeoJson(input, debugKey) {
414
414
  var import_jsx_runtime = require("react/jsx-runtime");
415
415
  var POINT_GEOMETRY_TYPES = /* @__PURE__ */ new Set(["Point", "MultiPoint"]);
416
416
  var DEV_MODE = typeof process !== "undefined" && process.env.NODE_ENV !== "production";
417
+ function getPolygonArea(coords) {
418
+ let area = 0;
419
+ for (let i = 0; i < coords.length - 1; i++) {
420
+ area += coords[i][0] * coords[i + 1][1] - coords[i + 1][0] * coords[i][1];
421
+ }
422
+ return Math.abs(area / 2);
423
+ }
424
+ function getFeatureArea(feature) {
425
+ const geom = feature?.geometry;
426
+ if (!geom?.coordinates) return 0;
427
+ try {
428
+ if (geom.type === "Polygon") return getPolygonArea(geom.coordinates[0]);
429
+ if (geom.type === "MultiPolygon") {
430
+ return geom.coordinates.reduce(
431
+ (sum, poly) => sum + getPolygonArea(poly[0]),
432
+ 0
433
+ );
434
+ }
435
+ } catch {
436
+ }
437
+ return 0;
438
+ }
417
439
  function normalizeBboxFromData(data) {
418
440
  const bboxCandidate = data.bbox;
419
441
  if (!Array.isArray(bboxCandidate) || bboxCandidate.length < 4) return null;
@@ -515,7 +537,10 @@ var LayerGeoJson = ({
515
537
  }, [onEachFeature]);
516
538
  const safeData = (0, import_react.useMemo)(() => sanitizeGeoJson(data, String(layerId)), [data, layerId]);
517
539
  const features = (0, import_react.useMemo)(() => safeData.features ?? [], [safeData]);
518
- const fillFeatures = (0, import_react.useMemo)(() => features.filter(isNonPointGeometry), [features]);
540
+ const fillFeatures = (0, import_react.useMemo)(
541
+ () => [...features.filter(isNonPointGeometry)].sort((a, b) => getFeatureArea(b) - getFeatureArea(a)),
542
+ [features]
543
+ );
519
544
  const pointFeatures = (0, import_react.useMemo)(() => features.filter(isPointGeometry), [features]);
520
545
  const dataVersionRef = (0, import_react.useRef)(0);
521
546
  const prevSignatureRef = (0, import_react.useRef)("");
@@ -560,7 +585,8 @@ var LayerGeoJson = ({
560
585
  return import_leaflet.default.marker(latlng, {
561
586
  icon: createPointDivIcon(style, isMobile),
562
587
  pane: clusterPaneName,
563
- interactive: true
588
+ interactive: true,
589
+ bubblingMouseEvents: false
564
590
  });
565
591
  },
566
592
  onEachFeature: (feature, layer) => onEachFeatureRef.current(feature, layer)
@@ -588,7 +614,7 @@ var LayerGeoJson = ({
588
614
  {
589
615
  data: fillData,
590
616
  pane: resolvedFillPane,
591
- style: (feature) => styleFn(feature, layerType, baseOpacity),
617
+ style: (feature) => ({ ...styleFn(feature, layerType, baseOpacity), bubblingMouseEvents: false }),
592
618
  onEachFeature: (feature, layer) => {
593
619
  onEachFeature(feature, layer);
594
620
  onPolygonLabel?.(feature, layer);
@@ -607,7 +633,9 @@ var LayerGeoJson = ({
607
633
  }
608
634
  return import_leaflet.default.circleMarker(latlng, {
609
635
  radius: isMobile ? 8 : 6,
610
- ...styleFn(feature, layerType, baseOpacity)
636
+ ...styleFn(feature, layerType, baseOpacity),
637
+ bubblingMouseEvents: false,
638
+ interactive: true
611
639
  });
612
640
  },
613
641
  onEachFeature
@@ -840,6 +868,7 @@ var POPUP_EXCLUDED_KEYS = /* @__PURE__ */ new Set(["geom", "geometry", "_private
840
868
  var POPUP_TITLE_KEYS = ["id", "nombre", "name", "title", "titulo", "cluster"];
841
869
  var POPUP_BADGE_KEYS = ["tipo", "type", "category", "categoria", "estado", "status"];
842
870
  var POPUP_DESCRIPTION_KEYS = ["descripcion", "description", "desc"];
871
+ var POPUP_PRIORITY_KEYS = ["insights", "recomendaciones"];
843
872
  var CURRENCY_KEYWORDS = [
844
873
  "capital",
845
874
  "monto",
@@ -1070,6 +1099,14 @@ function shouldIncludePopupEntry(key, value) {
1070
1099
  if (typeof value === "string" && !value.trim()) return false;
1071
1100
  return true;
1072
1101
  }
1102
+ function getPopupPriorityIndex(key) {
1103
+ const normalized = key.trim().toLowerCase();
1104
+ const idx = POPUP_PRIORITY_KEYS.indexOf(normalized);
1105
+ return idx === -1 ? POPUP_PRIORITY_KEYS.length : idx;
1106
+ }
1107
+ function sortEntriesByPriority(entries) {
1108
+ return [...entries].sort((a, b) => getPopupPriorityIndex(a[0]) - getPopupPriorityIndex(b[0]));
1109
+ }
1073
1110
  function createPopupContent(properties) {
1074
1111
  const header = findHeaderProperties(properties);
1075
1112
  const headerText = header.title?.value ?? extractPopupHeader(properties);
@@ -1077,11 +1114,13 @@ function createPopupContent(properties) {
1077
1114
  const badge = header.badge ? getBadgeForProperty(header.badge.key, header.badge.value) : null;
1078
1115
  const colorValue = properties.color;
1079
1116
  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>` : "";
1080
- const entries = Object.entries(properties).filter(([key, value]) => {
1081
- if (!shouldIncludePopupEntry(key, value)) return false;
1082
- if (usedKeys.has(key)) return false;
1083
- return true;
1084
- });
1117
+ const entries = sortEntriesByPriority(
1118
+ Object.entries(properties).filter(([key, value]) => {
1119
+ if (!shouldIncludePopupEntry(key, value)) return false;
1120
+ if (usedKeys.has(key)) return false;
1121
+ return true;
1122
+ })
1123
+ );
1085
1124
  if (entries.length === 0) {
1086
1125
  return '<div style="padding:8px 0; color:#64748b; text-align:center;">Sin datos disponibles</div>';
1087
1126
  }
@@ -1127,7 +1166,10 @@ function buildWhitelistedRows(properties, headerSections) {
1127
1166
  return null;
1128
1167
  }
1129
1168
  const { whitelist, modalType } = selection;
1130
- const entries = applyModalWhitelist(properties, whitelist);
1169
+ const rawEntries = applyModalWhitelist(properties, whitelist);
1170
+ const entries = [...rawEntries].sort(
1171
+ (a, b) => getPopupPriorityIndex(a.matchedKey) - getPopupPriorityIndex(b.matchedKey)
1172
+ );
1131
1173
  if (entries.length === 0) {
1132
1174
  logModalWhitelistDebug({
1133
1175
  modalType,
@@ -1678,6 +1720,39 @@ function isCandidateGeometryType(candidate, allowedTypes) {
1678
1720
  const geometryType = candidate?.geometry?.type;
1679
1721
  return Boolean(geometryType && allowedTypes.has(geometryType));
1680
1722
  }
1723
+ function calculatePolygonArea(coords) {
1724
+ let area = 0;
1725
+ for (let i = 0; i < coords.length - 1; i++) {
1726
+ area += coords[i][0] * coords[i + 1][1] - coords[i + 1][0] * coords[i][1];
1727
+ }
1728
+ return Math.abs(area / 2);
1729
+ }
1730
+ function calculateGeometryArea(geometry) {
1731
+ if (!geometry?.coordinates) return 0;
1732
+ try {
1733
+ if (geometry.type === "Polygon") return calculatePolygonArea(geometry.coordinates[0]);
1734
+ if (geometry.type === "MultiPolygon") {
1735
+ return geometry.coordinates.reduce(
1736
+ (sum, poly) => sum + calculatePolygonArea(poly[0]),
1737
+ 0
1738
+ );
1739
+ }
1740
+ } catch {
1741
+ }
1742
+ return 0;
1743
+ }
1744
+ function calculateLayerAverageArea(features) {
1745
+ if (!features?.length) return 0;
1746
+ try {
1747
+ const total = features.reduce(
1748
+ (sum, f) => sum + (f?.geometry ? calculateGeometryArea(f.geometry) : 0),
1749
+ 0
1750
+ );
1751
+ return total / features.length;
1752
+ } catch {
1753
+ return 0;
1754
+ }
1755
+ }
1681
1756
  function getFeatureStyleOverrides(feature) {
1682
1757
  const candidate = feature?.properties?._style;
1683
1758
  if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return null;
@@ -2039,7 +2114,7 @@ var ZenitMap = (0, import_react5.forwardRef)(({
2039
2114
  );
2040
2115
  if (layerStates && onLayerStateChange) {
2041
2116
  const next = effectiveStates.map(
2042
- (state) => state.layerId === layerId ? {
2117
+ (state) => isLayerIdMatch(state.layerId, layerId) ? {
2043
2118
  ...state,
2044
2119
  baseOpacity,
2045
2120
  opacity: effectiveOpacity,
@@ -2050,11 +2125,11 @@ var ZenitMap = (0, import_react5.forwardRef)(({
2050
2125
  return;
2051
2126
  }
2052
2127
  setBaseStates(
2053
- (prev) => prev.map((state) => state.layerId === layerId ? { ...state, baseOpacity } : state)
2128
+ (prev) => prev.map((state) => isLayerIdMatch(state.layerId, layerId) ? { ...state, baseOpacity } : state)
2054
2129
  );
2055
2130
  setUiOverrides((prev) => {
2056
- const existing = prev.find((entry) => entry.layerId === layerId);
2057
- const filtered = prev.filter((entry) => entry.layerId !== layerId);
2131
+ const existing = prev.find((entry) => isLayerIdMatch(entry.layerId, layerId));
2132
+ const filtered = prev.filter((entry) => !isLayerIdMatch(entry.layerId, layerId));
2058
2133
  if (existing && existing.overrideVisible !== void 0) {
2059
2134
  return [
2060
2135
  ...filtered,
@@ -2096,13 +2171,18 @@ var ZenitMap = (0, import_react5.forwardRef)(({
2096
2171
  const override = layerGeojsonOverrides[layerKey];
2097
2172
  return {
2098
2173
  ...layer,
2099
- effective: effectiveStates.find((state) => state.layerId === layer.mapLayer.layerId),
2174
+ effective: effectiveStates.find((state) => isLayerIdMatch(state.layerId, layer.mapLayer.layerId)),
2100
2175
  data: override ?? layerGeojson?.[layer.mapLayer.layerId] ?? layerGeojson?.[layerKey] ?? null
2101
2176
  };
2102
2177
  });
2103
2178
  }, [effectiveStates, layerGeojson, layerGeojsonOverrides, layers]);
2104
2179
  const orderedLayers = (0, import_react5.useMemo)(() => {
2105
- return [...decoratedLayers].filter((layer) => layer.effective?.visible && layer.data).sort((a, b) => a.displayOrder - b.displayOrder);
2180
+ return [...decoratedLayers].filter((layer) => layer.effective?.visible && layer.data).sort((a, b) => {
2181
+ const aArea = calculateLayerAverageArea(a.data?.features ?? []);
2182
+ const bArea = calculateLayerAverageArea(b.data?.features ?? []);
2183
+ if (aArea !== bArea) return bArea - aArea;
2184
+ return a.displayOrder - b.displayOrder;
2185
+ });
2106
2186
  }, [decoratedLayers]);
2107
2187
  const autoZoomGeojson = (0, import_react5.useMemo)(
2108
2188
  () => orderedLayers.map((layer) => layer.data).filter((collection) => !!collection),
@@ -2155,15 +2235,14 @@ var ZenitMap = (0, import_react5.forwardRef)(({
2155
2235
  (targetMap, targetLayers) => {
2156
2236
  const fillBaseZIndex = 400;
2157
2237
  const pointsBaseZIndex = 750;
2158
- targetLayers.forEach((layer) => {
2159
- const order = Number.isFinite(layer.displayOrder) ? layer.displayOrder : 0;
2160
- const orderOffset = Math.max(0, Math.min(order, 150));
2238
+ const sortedByArea = [...targetLayers].sort((a, b) => (b.averageArea ?? 0) - (a.averageArea ?? 0));
2239
+ sortedByArea.forEach((layer, areaIndex) => {
2161
2240
  const fillPaneName = `zenit-layer-${layer.layerId}-fill`;
2162
2241
  const pointPaneName = `zenit-layer-${layer.layerId}-points`;
2163
2242
  const fillPane = targetMap.getPane(fillPaneName) ?? targetMap.createPane(fillPaneName);
2164
2243
  const pointPane = targetMap.getPane(pointPaneName) ?? targetMap.createPane(pointPaneName);
2165
- fillPane.style.zIndex = String(fillBaseZIndex + orderOffset);
2166
- pointPane.style.zIndex = String(pointsBaseZIndex + orderOffset);
2244
+ fillPane.style.zIndex = String(fillBaseZIndex + areaIndex);
2245
+ pointPane.style.zIndex = String(pointsBaseZIndex + areaIndex);
2167
2246
  });
2168
2247
  },
2169
2248
  []
@@ -2186,10 +2265,11 @@ var ZenitMap = (0, import_react5.forwardRef)(({
2186
2265
  }
2187
2266
  const layerTargets = orderedLayers.map((layer) => ({
2188
2267
  layerId: layer.mapLayer.layerId,
2189
- displayOrder: layer.displayOrder
2268
+ displayOrder: layer.displayOrder,
2269
+ averageArea: calculateLayerAverageArea(layer.data?.features ?? [])
2190
2270
  }));
2191
2271
  if (overlayGeojson) {
2192
- layerTargets.push({ layerId: "overlay-geojson", displayOrder: 999 });
2272
+ layerTargets.push({ layerId: "overlay-geojson", displayOrder: 999, averageArea: 0 });
2193
2273
  }
2194
2274
  ensureLayerPanes(mapInstance, layerTargets);
2195
2275
  const first = layerTargets[0];
@@ -2239,9 +2319,7 @@ var ZenitMap = (0, import_react5.forwardRef)(({
2239
2319
  });
2240
2320
  }
2241
2321
  layer.on("click", (e) => {
2242
- if (clickIntent === "point") {
2243
- import_leaflet4.default.DomEvent.stopPropagation(e);
2244
- }
2322
+ import_leaflet4.default.DomEvent.stopPropagation(e);
2245
2323
  if (featureInfoMode === "popup" && client && layerId !== void 0 && !extractDescriptionValue(feature?.properties) && feature?.geometry) {
2246
2324
  if (DEV_MODE2) {
2247
2325
  console.debug("[ZenitMap] click/intersect:start", {
@@ -3181,8 +3259,6 @@ var CATALOG_FIELD_BLACKLIST = /* @__PURE__ */ new Set([
3181
3259
  "objectid",
3182
3260
  "OBJECTID",
3183
3261
  "objectId",
3184
- "gforms",
3185
- "GFORMS",
3186
3262
  "nomina",
3187
3263
  "NOMINA",
3188
3264
  "shape_area",
@@ -3244,7 +3320,8 @@ var ZenitLayerManager = ({
3244
3320
  onApplyLayerFilter,
3245
3321
  onClearLayerFilter,
3246
3322
  availableFilterLayers = [],
3247
- filterFieldWhitelist = []
3323
+ filterFieldWhitelist = [],
3324
+ showFiltersTab = true
3248
3325
  }) => {
3249
3326
  const [map, setMap] = (0, import_react8.useState)(null);
3250
3327
  const [loadingMap, setLoadingMap] = (0, import_react8.useState)(false);
@@ -3630,20 +3707,21 @@ var ZenitLayerManager = ({
3630
3707
  controller.abort();
3631
3708
  };
3632
3709
  }, [activeCatalogKey, activeTab, catalogByLayerField, catalogFieldsByLayer, client.layers, extractCatalogFieldMap, filterFieldWhitelist, selectedFilterField, selectedFilterLayer]);
3633
- const handleApplyFilter = import_react8.default.useCallback(async () => {
3634
- if (!selectedFilterLayer || !selectedFilterField || !selectedFilterValue || !onApplyLayerFilter) return;
3710
+ const handleApplyFilter = import_react8.default.useCallback(async (valueOverride) => {
3711
+ const value = valueOverride ?? selectedFilterValue;
3712
+ if (!selectedFilterLayer || !selectedFilterField || !value || !onApplyLayerFilter) return;
3635
3713
  setApplyingFilter(true);
3636
3714
  setFilterError(null);
3637
3715
  try {
3638
3716
  await onApplyLayerFilter({
3639
3717
  layerId: selectedFilterLayer.mapLayer.layerId,
3640
3718
  field: selectedFilterField,
3641
- value: selectedFilterValue
3719
+ value
3642
3720
  });
3643
3721
  setAppliedFilter({
3644
3722
  layerId: selectedFilterLayer.mapLayer.layerId,
3645
3723
  field: selectedFilterField,
3646
- value: selectedFilterValue
3724
+ value
3647
3725
  });
3648
3726
  } catch (error) {
3649
3727
  const message = error instanceof Error ? error.message : "No se pudo aplicar el filtro";
@@ -4022,7 +4100,7 @@ var ZenitLayerManager = ({
4022
4100
  ]
4023
4101
  }
4024
4102
  ),
4025
- !hasPrefilters && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
4103
+ !hasPrefilters && showFiltersTab && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
4026
4104
  "button",
4027
4105
  {
4028
4106
  type: "button",
@@ -4057,7 +4135,7 @@ var ZenitLayerManager = ({
4057
4135
  ) }),
4058
4136
  renderLayerCards()
4059
4137
  ] }),
4060
- !hasPrefilters && activeTab === "filters" && /* @__PURE__ */ (0, import_jsx_runtime6.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_runtime6.jsx)("div", { style: { color: "#64748b", fontSize: 13 }, children: "No hay filtros disponibles para las capas de este mapa." }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
4138
+ !hasPrefilters && showFiltersTab && activeTab === "filters" && /* @__PURE__ */ (0, import_jsx_runtime6.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_runtime6.jsx)("div", { style: { color: "#64748b", fontSize: 13 }, children: "No hay filtros disponibles para las capas de este mapa." }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
4061
4139
  filterableLayers.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { style: { display: "flex", flexDirection: "column", gap: 6, fontSize: 12, color: "#475569" }, children: [
4062
4140
  "Capa",
4063
4141
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
@@ -4096,7 +4174,10 @@ var ZenitLayerManager = ({
4096
4174
  value: selectedFilterValue,
4097
4175
  placeholder: "Seleccionar\u2026",
4098
4176
  searchPlaceholder: "Buscar valor\u2026",
4099
- onChange: (nextValue) => setSelectedFilterValue(nextValue),
4177
+ onChange: (nextValue) => {
4178
+ setSelectedFilterValue(nextValue);
4179
+ if (nextValue) handleApplyFilter(nextValue);
4180
+ },
4100
4181
  options: activeCatalogValues,
4101
4182
  disabled: loadingCatalog || activeCatalogValues.length === 0
4102
4183
  }
@@ -4118,7 +4199,7 @@ var ZenitLayerManager = ({
4118
4199
  type: "button",
4119
4200
  className: "zlm-panel-toggle",
4120
4201
  disabled: !selectedFilterValue || applyingFilter || !onApplyLayerFilter,
4121
- onClick: handleApplyFilter,
4202
+ onClick: () => handleApplyFilter(),
4122
4203
  children: applyingFilter ? "Aplicando\u2026" : "Aplicar"
4123
4204
  }
4124
4205
  ),