datastake-daf 0.6.816 → 0.6.817

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.
Files changed (41) hide show
  1. package/dist/components/index.js +1196 -1285
  2. package/dist/pages/index.js +1315 -579
  3. package/dist/services/index.js +202 -0
  4. package/dist/utils/index.js +28 -0
  5. package/package.json +1 -1
  6. package/src/@daf/core/components/Charts/RadarChart/index.jsx +3 -12
  7. package/src/@daf/core/components/Charts/style.js +1 -2
  8. package/src/@daf/core/components/Dashboard/Map/ChainIcon/index.js +123 -104
  9. package/src/@daf/core/components/Dashboard/Widget/VegetationWidget/index.jsx +4 -0
  10. package/src/@daf/core/components/Table/index.jsx +11 -6
  11. package/src/@daf/pages/Events/Activities/columns.js +15 -11
  12. package/src/@daf/pages/Events/Incidents/columns.js +15 -11
  13. package/src/@daf/pages/Events/Testimonials/columns.js +173 -0
  14. package/src/@daf/pages/Events/Testimonials/config.js +175 -0
  15. package/src/@daf/pages/Events/columns.js +7 -3
  16. package/src/@daf/pages/Locations/ConflictAreas/columns.js +140 -0
  17. package/src/@daf/pages/Locations/ConflictAreas/config.js +41 -0
  18. package/src/@daf/pages/Locations/MineSite/columns.js +21 -12
  19. package/src/@daf/pages/Locations/MineSite/config.js +2 -1
  20. package/src/@daf/pages/Locations/columns.js +7 -3
  21. package/src/@daf/pages/Stakeholders/ArmedGroups/columns.js +110 -0
  22. package/src/@daf/pages/Stakeholders/ArmedGroups/config.js +41 -0
  23. package/src/@daf/pages/Stakeholders/Operators/columns.js +30 -14
  24. package/src/@daf/pages/Stakeholders/Workers/columns.js +23 -13
  25. package/src/@daf/pages/Stakeholders/columns.js +8 -4
  26. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/BiodiversityHabitat/ObservedFauna.jsx +11 -6
  27. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MangroveGrowth/PlantedSpecies.jsx +10 -25
  28. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MangroveGrowth/SeedlingsHeight.jsx +13 -10
  29. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MangroveGrowth/VegetationHealth.jsx +4 -19
  30. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/SoilWaterProfile/SoilType.jsx +10 -22
  31. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/SoilWaterProfile/WaterQuality.jsx +10 -26
  32. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/chartHelpers.js +0 -74
  33. package/src/@daf/pages/TablePage/config.js +1 -1
  34. package/src/@daf/pages/TablePage/helper.js +45 -0
  35. package/src/@daf/services/EventsService.js +115 -0
  36. package/src/@daf/services/LinkedSubjects.js +1 -0
  37. package/src/@daf/services/WorkersService.js +80 -0
  38. package/src/helpers/errorHandling.js +142 -74
  39. package/src/services.js +3 -1
  40. package/src/utils.js +1 -1
  41. package/dist/style/datastake/mapbox-gl.css +0 -330
@@ -1458,6 +1458,11 @@ class LinkedSubjectsService extends BaseService {
1458
1458
  getForm({
1459
1459
  namespace
1460
1460
  }, language = "en", scope) {
1461
+ console.log({
1462
+ namespace,
1463
+ language,
1464
+ scope
1465
+ });
1461
1466
  return this.apiGet({
1462
1467
  url: `forms/${namespace === "documents" ? namespace : getNamespace(namespace)}`,
1463
1468
  isApp: true,
@@ -1727,6 +1732,201 @@ class PartnerService extends BaseService {
1727
1732
  }
1728
1733
  var PartnerService$1 = createLazyService(PartnerService);
1729
1734
 
1735
+ class EventsService extends BaseService {
1736
+ getForm({
1737
+ scope
1738
+ }, language = "en") {
1739
+ return this.apiGet({
1740
+ isApp: true,
1741
+ url: `/forms/event`,
1742
+ params: {
1743
+ scope,
1744
+ language
1745
+ }
1746
+ });
1747
+ }
1748
+ getWithModule({
1749
+ query,
1750
+ signal,
1751
+ module
1752
+ }) {
1753
+ return this.apiGet({
1754
+ isApp: true,
1755
+ url: `/${module}/event`,
1756
+ params: query,
1757
+ signal
1758
+ });
1759
+ }
1760
+ getOne({
1761
+ id,
1762
+ module,
1763
+ signal
1764
+ }) {
1765
+ return this.apiGet({
1766
+ url: `/${module}/event/${id}`,
1767
+ isApp: true,
1768
+ signal
1769
+ });
1770
+ }
1771
+ get(query, signal) {
1772
+ return this.apiGet({
1773
+ isApp: true,
1774
+ url: "/event",
1775
+ params: query,
1776
+ signal
1777
+ });
1778
+ }
1779
+ getData(id, sourceId, source, version) {
1780
+ return this.apiGet({
1781
+ isApp: true,
1782
+ url: `/event/${id}`,
1783
+ params: {
1784
+ authorId: sourceId,
1785
+ source,
1786
+ version
1787
+ }
1788
+ });
1789
+ }
1790
+ getLinking(query) {
1791
+ return this.apiGet({
1792
+ isApp: true,
1793
+ url: "/event/linking",
1794
+ params: query
1795
+ });
1796
+ }
1797
+ submit(payload) {
1798
+ if (payload.id) {
1799
+ return this.apiPut({
1800
+ isApp: true,
1801
+ url: `/event/${payload.id}`,
1802
+ data: filterCreateData(payload)
1803
+ });
1804
+ }
1805
+ if (payload?.form) {
1806
+ delete payload.form;
1807
+ return this.apiPut({
1808
+ isApp: true,
1809
+ url: "/event",
1810
+ data: filterCreateData(payload)
1811
+ });
1812
+ }
1813
+ return this.apiPost({
1814
+ isApp: true,
1815
+ url: "/event",
1816
+ data: filterCreateData(payload)
1817
+ });
1818
+ }
1819
+ submitStep(data, id) {
1820
+ return this.apiPut({
1821
+ isApp: true,
1822
+ url: `/event/submit/${id}`,
1823
+ data: filterCreateData(data)
1824
+ });
1825
+ }
1826
+ remove(id, data) {
1827
+ return this.apiDelete({
1828
+ isApp: true,
1829
+ url: `/event/${id}/remove`,
1830
+ data: data
1831
+ });
1832
+ }
1833
+ submitForm(id) {
1834
+ const app = window.globalServicesConfig.application;
1835
+ return this.apiPost({
1836
+ isApp: true,
1837
+ url: `/${app}/versioning/submit/Event/${id}`
1838
+ });
1839
+ }
1840
+ getOptions() {
1841
+ return this.apiGet({
1842
+ isApp: true,
1843
+ url: `/forms/options`,
1844
+ params: {
1845
+ id: "categoryOptions,eventsType,testimonialsType,eventCategory,countries,eventCategoryOptions"
1846
+ }
1847
+ });
1848
+ }
1849
+ }
1850
+ var EventsService$1 = createLazyService(EventsService);
1851
+
1852
+ class WorkersService extends BaseService {
1853
+ get(params) {
1854
+ return this.apiGet({
1855
+ isApp: true,
1856
+ url: "/stakeholder",
1857
+ params
1858
+ });
1859
+ }
1860
+ getForm(scope = "modalNashirikiWorker", language = "en") {
1861
+ return this.apiGet({
1862
+ isApp: true,
1863
+ url: `/forms/stakeholder`,
1864
+ params: {
1865
+ scope,
1866
+ language
1867
+ }
1868
+ });
1869
+ }
1870
+ getData(id, sourceId, version, source) {
1871
+ return this.apiGet({
1872
+ isApp: true,
1873
+ url: `/stakeholder/${id}`,
1874
+ params: {
1875
+ authorId: sourceId,
1876
+ version,
1877
+ source
1878
+ }
1879
+ });
1880
+ }
1881
+ submit(payload) {
1882
+ if (payload.id) {
1883
+ // const { id, ...data } = payload;
1884
+ return this.apiPut({
1885
+ isApp: true,
1886
+ url: `/stakeholder/${payload.id}`,
1887
+ data: filterCreateData(payload)
1888
+ });
1889
+ }
1890
+ if (payload?.form) {
1891
+ delete payload.form;
1892
+ return this.apiPost({
1893
+ isApp: true,
1894
+ url: "/stakeholder",
1895
+ data: filterCreateData(payload)
1896
+ });
1897
+ }
1898
+ return this.apiPost({
1899
+ isApp: true,
1900
+ url: "/stakeholder",
1901
+ data: filterCreateData(payload)
1902
+ });
1903
+ }
1904
+ submitStep(data, id) {
1905
+ return this.apiPut({
1906
+ isApp: true,
1907
+ url: `/stakeholder/submit/${id}`,
1908
+ data: filterCreateData(data)
1909
+ });
1910
+ }
1911
+ remove(id, data) {
1912
+ return this.apiDelete({
1913
+ isApp: true,
1914
+ url: `/stakeholder/${id}/remove`,
1915
+ data: data
1916
+ });
1917
+ }
1918
+ getOptions() {
1919
+ return this.apiGet({
1920
+ isApp: true,
1921
+ url: `/forms/options`,
1922
+ params: {
1923
+ id: "activityAtSiteOptions,category,countries,optionPositionSupplyChain,subCategory"
1924
+ }
1925
+ });
1926
+ }
1927
+ }
1928
+ var WorkersService$1 = createLazyService(WorkersService);
1929
+
1730
1930
  exports.AdminService = AdminService$1;
1731
1931
  exports.AuthenticationService = AuthenticationService$1;
1732
1932
  exports.BaseHTTPService = BaseHTTPService;
@@ -1736,6 +1936,7 @@ exports.DashboardService = DashboardService$1;
1736
1936
  exports.DataStoreService = DataStoreService$1;
1737
1937
  exports.ErrorHandler = ErrorHandler;
1738
1938
  exports.ErrorService = ErrorService;
1939
+ exports.EventsService = EventsService$1;
1739
1940
  exports.LinkedSubjectsService = LinkedSubjects;
1740
1941
  exports.NotificationService = NotificationService$1;
1741
1942
  exports.OperatorService = OperatorService$1;
@@ -1743,6 +1944,7 @@ exports.PartnerService = PartnerService$1;
1743
1944
  exports.QueryService = QueryService$1;
1744
1945
  exports.SourceService = SourceService$1;
1745
1946
  exports.UserService = UserService$1;
1947
+ exports.WorkersService = WorkersService$1;
1746
1948
  exports.configureServices = configureServices;
1747
1949
  exports.createLazyService = createLazyService;
1748
1950
  exports.getServicesConfig = getServicesConfig;
@@ -14105,6 +14105,32 @@ const buildQueryString = params => {
14105
14105
  return query ? `?${query}` : '';
14106
14106
  };
14107
14107
 
14108
+ /**
14109
+ * Check if a successful response contains embedded error data
14110
+ */
14111
+ const isErrorResponse = data => {
14112
+ if (!data) return false;
14113
+ return (
14114
+ // Check for Exception names
14115
+ data.name && data.name.includes('Exception') ||
14116
+ // Check for nested response with error status
14117
+ data.response?.statusCode && data.response.statusCode >= 400 ||
14118
+ // Check for top-level error status
14119
+ data.statusCode && data.statusCode >= 400 ||
14120
+ // Check for explicit error flag
14121
+ data.error === true ||
14122
+ // Check for nested error property
14123
+ data.response?.error && data.response.error !== null
14124
+ );
14125
+ };
14126
+
14127
+ /**
14128
+ * Extract error message from various error response formats
14129
+ */
14130
+ const getErrorMessage = data => {
14131
+ return data?.message || data?.response?.message || data?.response?.error || 'An error occurred';
14132
+ };
14133
+
14108
14134
  /**
14109
14135
  * Generic error handler factory for axios requests
14110
14136
  * Highly configurable to adapt to different project needs
@@ -15091,6 +15117,7 @@ exports.formatToKebabCase = formatToKebabCase$1;
15091
15117
  exports.getAdminLevelName = getAdminLevelName;
15092
15118
  exports.getDefaultActiveFilters = getDefaultActiveFilters;
15093
15119
  exports.getDivergenceOrEqual = getDivergenceOrEqual;
15120
+ exports.getErrorMessage = getErrorMessage;
15094
15121
  exports.getImageUploadViewValue = getImageUploadViewValue;
15095
15122
  exports.getInterface = getInterface;
15096
15123
  exports.getNkey = getNkey;
@@ -15114,6 +15141,7 @@ exports.hasKeyInObject = hasKeyInObject;
15114
15141
  exports.hasNotChanged = hasNotChanged;
15115
15142
  exports.isArrayOfObjects = isArrayOfObjects;
15116
15143
  exports.isEmptyOrSpaces = isEmptyOrSpaces;
15144
+ exports.isErrorResponse = isErrorResponse;
15117
15145
  exports.isModuleApproved = isModuleApproved;
15118
15146
  exports.isProxy = isProxy;
15119
15147
  exports.isSuperAdmin = isSuperAdmin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datastake-daf",
3
- "version": "0.6.816",
3
+ "version": "0.6.817",
4
4
  "dependencies": {
5
5
  "@ant-design/icons": "^5.2.5",
6
6
  "@antv/g2": "^5.1.1",
@@ -59,18 +59,9 @@ const RadarChart = ({
59
59
  }
60
60
  : { showCrosshairs: false, showMarkers: true, ...tooltipConfig },
61
61
  color: color || token.colorPrimary7,
62
-
63
- paddingX: 60,
64
- paddingY: 60,
65
62
  xAxis: {
66
63
  label: {
67
64
  formatter: formattedXAxis,
68
- offset: 15,
69
- style: {
70
- fontSize: 12,
71
- fill: '#666',
72
- textAlign: 'center',
73
- },
74
65
  },
75
66
  line: null,
76
67
  tickLine: null,
@@ -151,9 +142,9 @@ const RadarChart = ({
151
142
  }, []);
152
143
 
153
144
  return (
154
- <div className="flex flex-1 flex-column justify-content-center" style={{ overflow: 'visible' }}>
155
- <div className="flex justify-content-center" style={{ paddingX: '30px', overflow: 'visible', width: '100%' }}>
156
- <Container ref={containerRef} height={height} isPdf={isPdf} style={{ paddingX: '30px' }}></Container>
145
+ <div className="flex flex-1 flex-column justify-content-center">
146
+ <div className="flex justify-content-center">
147
+ <Container ref={containerRef} height={height} isPdf={isPdf}></Container>
157
148
  </div>
158
149
  {legendEnabled && legendPosition === 'bottom' && (
159
150
  <CustomLegend
@@ -2,8 +2,7 @@ import styled from "styled-components";
2
2
 
3
3
  const Container = styled.div`
4
4
  height: ${props => props.height || '300px'};
5
- width: ${props => props.isPdf ? (props.width ? props.width : '1000px') : 'calc(100% - 48px)'};
6
- overflow: ${props => props.style?.overflow || 'visible'};
5
+ width: ${props => props.isPdf ? (props.width ? props.width : '1000px') : 'calc(100% - 48px)'};
7
6
  `;
8
7
 
9
8
  export default Container;
@@ -1,4 +1,5 @@
1
1
  import { useMemo, useEffect, useRef, useState } from "react";
2
+ import { createPortal } from "react-dom";
2
3
  import {
3
4
  isMineSite,
4
5
  getStakeholderPosition,
@@ -10,7 +11,6 @@ import {
10
11
  import MineSiteMarker from "./Markers/SVG/MinesiteMarker.js";
11
12
  import VillageMarker from "./Markers/SVG/VillageMarker.js";
12
13
  import StakeholderIcon from "./Markers/StakeholderMarker.js";
13
- import { createRoot } from "react-dom/client";
14
14
  import * as L from "leaflet";
15
15
  import { Popover } from "antd";
16
16
  import { renderTooltipJsx } from "../../../../../utils/tooltip.js";
@@ -32,8 +32,8 @@ export default function LocationIcon({
32
32
  activeMarker,
33
33
  setActiveMarker,
34
34
  }) {
35
- const rootsMapRef = useRef(new Map());
36
- const markersRef = useRef([])
35
+ const markersRef = useRef([]);
36
+ const [portalContainers, setPortalContainers] = useState([]);
37
37
  const isSelected = selectedMarkersId.includes(data.datastakeId);
38
38
  const Marker = useMemo(() => {
39
39
  if (isMineSite(data.type)) {
@@ -114,25 +114,31 @@ export default function LocationIcon({
114
114
  }, [JSON.stringify(allData), JSON.stringify(data.links), JSON.stringify(data.stakeholders), zoom]);
115
115
 
116
116
  const stakeholdersOfLocation = useMemo(() => {
117
- return data?.stakeholders || [];
118
- }, [data.stakeholders, zoom]);
117
+ return (data?.stakeholders || []).filter(stakeholder => {
118
+ if (!stakeholder.links || stakeholder.links.length === 0) {
119
+ return true;
120
+ }
121
+
122
+ const locationsWithThisStakeholder = allData
123
+ .filter(loc =>
124
+ loc.stakeholders?.some(s => s.datastakeId === stakeholder.datastakeId)
125
+ )
126
+ .map(loc => loc.datastakeId);
127
+
128
+ const primaryLocation = locationsWithThisStakeholder.sort()[0];
129
+ return data.datastakeId === primaryLocation;
130
+ });
131
+ }, [data.stakeholders, data.datastakeId, allData, zoom]);
119
132
 
120
133
  useEffect(() => {
121
- const currentRoots = rootsMapRef.current;
122
- const currentMarkers = markersRef.current;
123
-
124
- currentMarkers.forEach(marker => {
125
- if (mapRef.hasLayer(marker)) {
134
+ markersRef.current.forEach(marker => {
135
+ if (mapRef && mapRef.hasLayer(marker)) {
126
136
  mapRef.removeLayer(marker);
127
137
  }
128
138
  });
129
- currentRoots.forEach(root => {
130
- root.unmount();
131
- });
132
- currentRoots.clear();
133
139
  markersRef.current = [];
140
+ setPortalContainers([]);
134
141
 
135
- // Only create stakeholder markers if this location or any of its stakeholders are selected
136
142
  const shouldShowStakeholders = isSelected || stakeholdersOfLocation.some(stk =>
137
143
  selectedMarkersId.includes(stk.datastakeId)
138
144
  );
@@ -141,9 +147,11 @@ export default function LocationIcon({
141
147
  return;
142
148
  }
143
149
 
144
- // Create new markers only when selected
150
+ // Create markers and store their container references
151
+ const containers = [];
152
+
145
153
  stakeholdersOfLocation.forEach((stakeholder, index) => {
146
- const markerId = `${stakeholder.datastakeId}`;
154
+ const markerId = `${data.datastakeId}-${stakeholder.datastakeId}`;
147
155
  const { x, y, radius, center } = getStakeholderPosition({
148
156
  zoom,
149
157
  totalMarkers: stakeholdersOfLocation.length,
@@ -162,46 +170,30 @@ export default function LocationIcon({
162
170
  const isForceOpen = activeMarker?.datastakeId === data.datastakeId;
163
171
  const iconSize = isSmallMarker(zoom) || isExtraSmallMarker(zoom) ? [11, 11] : [25, 25];
164
172
 
173
+ // Create container div
174
+ const containerDiv = document.createElement('div');
175
+ containerDiv.id = markerId;
176
+
165
177
  const marker = L.marker(stakeholderLatLng, {
166
178
  icon: L.divIcon({
167
- html: `<div id="${markerId}"></div>`,
179
+ html: containerDiv.outerHTML,
168
180
  className: "marker-chain",
169
181
  iconSize: iconSize,
170
182
  }),
171
183
  }).addTo(mapRef);
172
184
 
173
185
  markersRef.current.push(marker);
174
-
175
- setTimeout(() => {
176
- const div = document.getElementById(markerId);
177
- if (div && !rootsMapRef.current.has(markerId)) {
178
- const root = createRoot(div);
179
- rootsMapRef.current.set(markerId, root);
180
-
181
- root.render(
182
- <StakeholderIcon
183
- data={stakeholder}
184
- zoom={zoom}
185
- allData={allData}
186
- link={link}
187
- parentId={data.datastakeId}
188
- renderTooltip={renderTooltip}
189
- onClickLink={onClickLink}
190
- selectedMarkersId={selectedMarkersId}
191
- handleSelectMarker={handleSelectMarker}
192
- mapRef={mapRef}
193
- radius={radius}
194
- index={index}
195
- x={x}
196
- y={y}
197
- openPopupIdRef={openPopupIdRef}
198
- polylinesRef={polylinesRef}
199
- isForceOpen={isForceOpen}
200
- activeMarker={activeMarker}
201
- />,
202
- );
203
- }
204
- }, 0);
186
+
187
+ // Store container info for portal rendering
188
+ containers.push({
189
+ markerId,
190
+ stakeholder,
191
+ x,
192
+ y,
193
+ radius,
194
+ index,
195
+ isForceOpen,
196
+ });
205
197
 
206
198
  setMapMarkers((prev) => {
207
199
  const array = [
@@ -236,21 +228,23 @@ export default function LocationIcon({
236
228
  isForceOpen,
237
229
  listOfPolylines: polylinesRef.current,
238
230
  stakeholderType: stakeholder.type,
239
- animated: true,
231
+ animated: true,
240
232
  });
241
233
  });
242
234
 
235
+ // Update portal containers after markers are created
236
+ setTimeout(() => {
237
+ setPortalContainers(containers);
238
+ }, 0);
239
+
243
240
  return () => {
244
241
  markersRef.current.forEach(marker => {
245
- if (mapRef.hasLayer(marker)) {
242
+ if (mapRef && mapRef.hasLayer(marker)) {
246
243
  mapRef.removeLayer(marker);
247
244
  }
248
245
  });
249
- rootsMapRef.current.forEach(root => {
250
- root.unmount();
251
- });
252
- rootsMapRef.current.clear();
253
246
  markersRef.current = [];
247
+ setPortalContainers([]);
254
248
  };
255
249
  }, [stakeholdersOfLocation, selectedMarkersId, activeMarker, zoom]);
256
250
 
@@ -344,56 +338,81 @@ useEffect(() => {
344
338
  }, [linkedNodesData, selectedMarkersId, zoom, stakeholdersOfLocation, isSelected]);
345
339
 
346
340
  return (
347
- <Popover
348
- content={renderTooltipJsx({
349
- title: data.name,
350
- subTitle: data.subTitle,
351
- total: data.sources,
352
- className: "pt-0 pb-0",
353
- items: renderTooltip(data),
354
- link,
355
- onClickLink: () => onClickLink(data),
356
- isNewTab: true,
357
- })}
358
- // open={
359
- // (!openPopupIdRef.current || openPopupIdRef.current === data.datastakeId) &&
360
- // isHovering
361
- // }
362
- getPopupContainer={(triggerNode) => {
363
- const mapElement = document.getElementById("map");
364
- return mapElement || triggerNode.parentElement || document.body;
365
- }}
366
- >
367
- <div style={{ position: "relative", display: "inline-block" }}>
368
- {(isSelected || selectedMarkersId.length === 0) && (
369
- <div
370
- style={{
371
- position: "absolute",
372
- bottom: "0",
373
- left: "50%",
374
- transform: "translateX(-50%)",
375
- width: "12px",
376
- height: "6px",
377
- background: "rgba(0,0,0,0.15)",
378
- borderRadius: "50%",
379
- filter: "blur(3px)",
341
+ <>
342
+ <Popover
343
+ content={renderTooltipJsx({
344
+ title: data.name,
345
+ subTitle: data.subTitle,
346
+ total: data.sources,
347
+ className: "pt-0 pb-0",
348
+ items: renderTooltip(data),
349
+ link,
350
+ onClickLink: () => onClickLink(data),
351
+ isNewTab: true,
352
+ })}
353
+ getPopupContainer={(triggerNode) => {
354
+ const mapElement = document.getElementById("map");
355
+ return mapElement || triggerNode.parentElement || document.body;
356
+ }}
357
+ >
358
+ <div style={{ position: "relative", display: "inline-block" }}>
359
+ {(isSelected || selectedMarkersId.length === 0) && (
360
+ <div
361
+ style={{
362
+ position: "absolute",
363
+ bottom: "0",
364
+ left: "50%",
365
+ transform: "translateX(-50%)",
366
+ width: "12px",
367
+ height: "6px",
368
+ background: "rgba(0,0,0,0.15)",
369
+ borderRadius: "50%",
370
+ filter: "blur(3px)",
371
+ }}
372
+ />
373
+ )}
374
+ <Marker
375
+ isSelected={isSelected}
376
+ onClick={() => {
377
+ handleSelectMarker(data);
378
+ setActiveMarker(isSelected ? null : data);
380
379
  }}
380
+ zoom={zoom}
381
+ selectedMarkersId={selectedMarkersId}
381
382
  />
382
- )}
383
- <Marker
384
- isSelected={isSelected}
385
- onClick={() => {
386
- handleSelectMarker(data);
387
- setActiveMarker(isSelected ? null : data);
388
- }}
389
- zoom={zoom}
390
- onMouseEnter={() => setIsHovering(true)}
391
- onMouseLeave={() => {
392
- setIsHovering(false);
393
- }}
394
- selectedMarkersId={selectedMarkersId}
395
- />
396
- </div>
397
- </Popover>
383
+ </div>
384
+ </Popover>
385
+
386
+ {/* Render stakeholder icons via portals */}
387
+ {portalContainers.map(({markerId, stakeholder, x, y, radius, index, isForceOpen}) => {
388
+ const container = document.getElementById(markerId);
389
+ if (!container) return null;
390
+
391
+ return createPortal(
392
+ <StakeholderIcon
393
+ key={markerId}
394
+ data={stakeholder}
395
+ zoom={zoom}
396
+ allData={allData}
397
+ link={link}
398
+ parentId={data.datastakeId}
399
+ renderTooltip={renderTooltip}
400
+ onClickLink={onClickLink}
401
+ selectedMarkersId={selectedMarkersId}
402
+ handleSelectMarker={handleSelectMarker}
403
+ mapRef={mapRef}
404
+ radius={radius}
405
+ index={index}
406
+ x={x}
407
+ y={y}
408
+ openPopupIdRef={openPopupIdRef}
409
+ polylinesRef={polylinesRef}
410
+ isForceOpen={isForceOpen}
411
+ activeMarker={activeMarker}
412
+ />,
413
+ container
414
+ );
415
+ })}
416
+ </>
398
417
  );
399
418
  }
@@ -16,12 +16,16 @@ export default function VegetationWidget({
16
16
  }) {
17
17
  let vegetationConfig = getVegetationConfig();
18
18
 
19
+ // Get all VEGETATION_KEYS values before filtering (needed for mapping check)
19
20
  const allVegetationKeys = vegetationConfig.map(item => item.key);
20
21
 
22
+ // Filter to show only specific keys if filterKeys is provided
21
23
  if (filterKeys && Array.isArray(filterKeys)) {
22
24
  vegetationConfig = vegetationConfig.filter(item => filterKeys.includes(item.key));
23
25
  }
24
26
 
27
+ // Map growthObservations to VEGETATION_KEYS
28
+ // Handle both formats: growthObservations keys (e.g., "yellowing_leaves") and VEGETATION_KEYS (e.g., "yellowing")
25
29
  const mappedGrowthObservations = Array.isArray(growthObservations)
26
30
  ? growthObservations
27
31
  .map(obs => {
@@ -36,7 +36,12 @@ export default function DAFTable({
36
36
  doEmptyRows,
37
37
  ...rest
38
38
  }) {
39
- const [source, setSource] = useState([]);
39
+ const source = useMemo(() => {
40
+ if (data && Array.isArray(data)) {
41
+ return data;
42
+ }
43
+ return [];
44
+ }, [data]);
40
45
  const projectData = (projects || []).find(p => p.id === selectedProject);
41
46
  const [filtersInit, setFiltersInit] = useState(!loading);
42
47
 
@@ -85,11 +90,11 @@ export default function DAFTable({
85
90
  } : filtersConfig;
86
91
  }, [sourcesKey, sources, filtersConfig, t]);
87
92
 
88
- useEffect(() => {
89
- if (data && Array.isArray(data)) {
90
- setSource(data);
91
- }
92
- }, [data, data.length]);
93
+ // useEffect(() => {
94
+ // if (data && Array.isArray(data)) {
95
+ // setSource(data);
96
+ // }
97
+ // }, [data, data.length]);
93
98
 
94
99
  const paginationPageSize = pagination?.pageSize;
95
100
  const dataSource = useMemo(() => {