datastake-daf 0.6.781 → 0.6.783

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 (28) hide show
  1. package/dist/components/index.js +383 -317
  2. package/dist/pages/index.js +2577 -258
  3. package/dist/utils/index.js +13 -0
  4. package/package.json +1 -1
  5. package/src/@daf/core/components/Dashboard/Map/ChainIcon/Markers/StakeholderMarker.js +9 -76
  6. package/src/@daf/core/components/Dashboard/Map/ChainIcon/index.js +116 -8
  7. package/src/@daf/core/components/Dashboard/Map/ChainIcon/utils.js +73 -17
  8. package/src/@daf/core/components/Dashboard/Map/helper.js +1 -0
  9. package/src/@daf/core/components/Dashboard/Map/hook.js +64 -29
  10. package/src/@daf/core/components/Dashboard/Map/style.js +20 -5
  11. package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CycleOutcomes/index.jsx +1 -1
  12. package/src/@daf/pages/Summary/Activities/PlantingCycle/components/PlantingLocations/index.jsx +2 -2
  13. package/src/@daf/pages/Template/components/LinkingTemplate/columns.js +95 -0
  14. package/src/@daf/pages/Template/components/LinkingTemplate/config.js +75 -0
  15. package/src/@daf/pages/Template/components/LinkingTemplate/index.jsx +119 -0
  16. package/src/@daf/pages/Template/index.jsx +19 -0
  17. package/src/@daf/pages/View/hooks/useCallToGetData.js +73 -0
  18. package/src/@daf/pages/View/hooks/usePrepareForm.js +86 -0
  19. package/src/@daf/pages/View/hooks/useSubmitSubject.js +40 -0
  20. package/src/@daf/pages/View/hooks/useViewActions.js +83 -0
  21. package/src/@daf/pages/View/hooks/useViewPermissions.js +74 -0
  22. package/src/@daf/pages/View/hooks/useViewUrlParams.js +93 -0
  23. package/src/@daf/pages/View/index.jsx +325 -0
  24. package/src/@daf/utils/object.js +3 -1
  25. package/src/pages.js +4 -1
  26. package/src/utils.js +1 -1
  27. package/dist/style/datastake/mapbox-gl.css +0 -330
  28. package/src/@daf/hooks/useViewFormUrlParams.js +0 -84
@@ -7211,6 +7211,17 @@ function convertUndefinedToNull(obj) {
7211
7211
  }
7212
7212
  return obj;
7213
7213
  }
7214
+ const removeKeysFromObject = (obj = {}, keys = []) => {
7215
+ if (typeof obj !== 'object' || obj === null) return obj;
7216
+ const result = {};
7217
+ for (const key of Object.keys(obj)) {
7218
+ if (!keys.includes(key)) {
7219
+ result[key] = obj[key];
7220
+ }
7221
+ }
7222
+ return result;
7223
+ };
7224
+ const hasKeyInObject = (obj, key) => Object.keys(obj || {}).includes(key);
7214
7225
 
7215
7226
  ({
7216
7227
  t: PropTypes__default["default"].func,
@@ -14935,6 +14946,7 @@ exports.handleError = handleError;
14935
14946
  exports.handleInfo = handleInfo;
14936
14947
  exports.handleSuccess = handleSuccess;
14937
14948
  exports.handleWarning = handleWarning;
14949
+ exports.hasKeyInObject = hasKeyInObject;
14938
14950
  exports.hasNotChanged = hasNotChanged;
14939
14951
  exports.isEmptyOrSpaces = isEmptyOrSpaces;
14940
14952
  exports.isModuleApproved = isModuleApproved;
@@ -14950,6 +14962,7 @@ exports.modules = modules;
14950
14962
  exports.nowToIso = nowToIso;
14951
14963
  exports.processConfig = processConfig;
14952
14964
  exports.propHasValue = propHasValue;
14965
+ exports.removeKeysFromObject = removeKeysFromObject;
14953
14966
  exports.renderBreadCrumbs = renderBreadCrumbs;
14954
14967
  exports.renderDateFormatted = renderDateFormatted;
14955
14968
  exports.renderNumber = renderNumber;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datastake-daf",
3
- "version": "0.6.781",
3
+ "version": "0.6.783",
4
4
  "dependencies": {
5
5
  "@ant-design/icons": "^5.2.5",
6
6
  "@antv/g2": "^5.1.1",
@@ -88,6 +88,10 @@ export default function StakeholderIcon({
88
88
  }, [parentId, allData]);
89
89
 
90
90
  useEffect(() => {
91
+ if (selectedMarkersId.length === 0 || !isSelected) {
92
+ return;
93
+ }
94
+
91
95
  linkNodesData.map((node) => {
92
96
  const isConnectingToStakeholder = node.isStakeholder;
93
97
  const id = `${data.datastakeId}-${node.stakeholderId || node.datastakeId}`;
@@ -101,13 +105,6 @@ export default function StakeholderIcon({
101
105
  const stakeholderLatLng = mapRef.layerPointToLatLng(stakeholderPoint);
102
106
  let endLatLng = L.latLng(node.gps.latitude, node.gps.longitude);
103
107
 
104
- const areNextToEachOther =
105
- targetMarkerIndex === index + 1 ||
106
- targetMarkerIndex === index - 1 ||
107
- (index === 0 && targetMarkerIndex === node.totalStakeholders - 1) ||
108
- (targetMarkerIndex === 0 && index === node.totalStakeholders - 1);
109
- const areOnlyTwoSiblings = node.totalStakeholders === 2;
110
-
111
108
  if (isExtraSmallMarker(zoom) && !isForceOpen) {
112
109
  createPolyline({
113
110
  L,
@@ -118,6 +115,7 @@ export default function StakeholderIcon({
118
115
  isSelected,
119
116
  id,
120
117
  listOfPolylines: polylinesRef.current,
118
+ animated: true,
121
119
  });
122
120
  return;
123
121
  }
@@ -137,75 +135,8 @@ export default function StakeholderIcon({
137
135
  y + nodePoint.y + center.top,
138
136
  );
139
137
  endLatLng = mapRef.layerPointToLatLng(endPoint);
140
-
141
- if (isSibling && (!areNextToEachOther || areOnlyTwoSiblings)) {
142
- if (areOnlyTwoSiblings) {
143
- buildCurveWIthTwoSiblings({
144
- mapRef,
145
- startLatLng: stakeholderLatLng,
146
- endLatLng,
147
- zoom,
148
- isSelected,
149
- id,
150
- });
151
- return;
152
- }
153
-
154
- const total = node.totalStakeholders;
155
- let from = index;
156
- let to = targetMarkerIndex;
157
- let flip = false;
158
-
159
- const forwardDistance = (to - from + total) % total;
160
- const backwardDistance = (from - to + total) % total;
161
-
162
- if (backwardDistance < forwardDistance) {
163
- [from, to] = [to, from];
164
- flip = true;
165
- }
166
-
167
- const intermediateIndices = [];
168
- for (let i = 1; i < (to - from + total) % total; i++) {
169
- intermediateIndices.push((from + i) % total);
170
- }
171
-
172
- const indices = [from, ...intermediateIndices, to];
173
-
174
- const intermediatePoints = [];
175
-
176
- for (const i of indices) {
177
- const { x, y } = createCurvePath({
178
- zoom,
179
- totalMarkers: node.totalStakeholders,
180
- markerIndex: i,
181
- });
182
-
183
- const point = centerPoint.add(L.point(x, y));
184
- const latlng = mapRef.layerPointToLatLng(point);
185
- intermediatePoints.push(latlng);
186
- }
187
-
188
- const latlngs = flip
189
- ? [endLatLng, ...intermediatePoints, stakeholderLatLng]
190
- : [stakeholderLatLng, ...intermediatePoints, endLatLng];
191
-
192
- const layerPoints = latlngs.map((latlng) => mapRef.latLngToLayerPoint(latlng));
193
-
194
- const path = buildSmoothCurve(layerPoints, mapRef, flip);
195
-
196
- const curve = L?.curve?.(path, {
197
- color: "var(--base-gray-70)",
198
- weight: isExtraSmallMarker(zoom) ? 0 : 1,
199
- opacity: isSelected ? 1 : 0.5,
200
- smoothFactor: 1,
201
- id,
202
- });
203
-
204
- mapRef.addLayer(curve);
205
- return;
206
- }
207
138
  }
208
-
139
+ // Always use straight lines
209
140
  createPolyline({
210
141
  L,
211
142
  mapRef,
@@ -216,9 +147,10 @@ export default function StakeholderIcon({
216
147
  isSelected,
217
148
  id,
218
149
  listOfPolylines: polylinesRef.current,
150
+ animated: true,
219
151
  });
220
152
  });
221
- }, [mapRef, x, y, parentData, linkNodesData, isSelected, zoom, isForceOpen]);
153
+ }, [mapRef, x, y, parentData, linkNodesData, isSelected, zoom, isForceOpen, selectedMarkersId]);
222
154
 
223
155
  return (
224
156
  <>
@@ -233,6 +165,7 @@ export default function StakeholderIcon({
233
165
  onClickLink: () => {
234
166
  onClickLink(data);
235
167
  },
168
+ isNewTab: true,
236
169
  })}
237
170
  getPopupContainer={(triggerNode) => {
238
171
  const mapElement = document.getElementById("map");
@@ -48,6 +48,7 @@ export default function LocationIcon({
48
48
  const nodes = [];
49
49
  const links = data.links || [];
50
50
 
51
+ // Add links from the location itself
51
52
  links.forEach((link) => {
52
53
  allData.forEach((d) => {
53
54
  if (d.datastakeId === link) {
@@ -68,8 +69,49 @@ export default function LocationIcon({
68
69
  });
69
70
  });
70
71
 
72
+ // ADD: Also include links from this location's stakeholders
73
+ const stakeholders = data.stakeholders || [];
74
+ stakeholders.forEach((stakeholder) => {
75
+ const stakeholderLinks = stakeholder.links || [];
76
+ stakeholderLinks.forEach((link) => {
77
+ allData.forEach((d) => {
78
+ // Check if it's a direct location link
79
+ if (d.datastakeId === link) {
80
+ // Avoid duplicates
81
+ if (!nodes.find(n => n.datastakeId === link && !n.isStakeholder)) {
82
+ nodes.push({
83
+ ...d,
84
+ fromStakeholderId: stakeholder.datastakeId,
85
+ });
86
+ }
87
+ }
88
+ // Check if it's a stakeholder link
89
+ if (d.stakeholders && d.stakeholders.length > 0) {
90
+ d.stakeholders.forEach((targetStakeholder) => {
91
+ if (targetStakeholder.datastakeId === link) {
92
+ // Avoid duplicates
93
+ if (!nodes.find(n =>
94
+ n.isStakeholder &&
95
+ n.datastakeId === d.datastakeId &&
96
+ n.stakeholdersIndex === d.stakeholders.indexOf(targetStakeholder)
97
+ )) {
98
+ nodes.push({
99
+ ...d,
100
+ isStakeholder: true,
101
+ totalStakeholders: d.stakeholders.length,
102
+ stakeholdersIndex: d.stakeholders.indexOf(targetStakeholder),
103
+ fromStakeholderId: stakeholder.datastakeId,
104
+ });
105
+ }
106
+ }
107
+ });
108
+ }
109
+ });
110
+ });
111
+ });
112
+
71
113
  return nodes;
72
- }, [JSON.stringify(allData), JSON.stringify(data.links), zoom]);
114
+ }, [JSON.stringify(allData), JSON.stringify(data.links), JSON.stringify(data.stakeholders), zoom]);
73
115
 
74
116
  const stakeholdersOfLocation = useMemo(() => {
75
117
  return data?.stakeholders || [];
@@ -90,7 +132,16 @@ export default function LocationIcon({
90
132
  currentRoots.clear();
91
133
  markersRef.current = [];
92
134
 
93
- // Create new markers
135
+ // Only create stakeholder markers if this location or any of its stakeholders are selected
136
+ const shouldShowStakeholders = isSelected || stakeholdersOfLocation.some(stk =>
137
+ selectedMarkersId.includes(stk.datastakeId)
138
+ );
139
+
140
+ if (!shouldShowStakeholders || selectedMarkersId.length === 0) {
141
+ return;
142
+ }
143
+
144
+ // Create new markers only when selected
94
145
  stakeholdersOfLocation.forEach((stakeholder, index) => {
95
146
  const markerId = `${stakeholder.datastakeId}`;
96
147
  const { x, y, radius, center } = getStakeholderPosition({
@@ -184,6 +235,8 @@ export default function LocationIcon({
184
235
  isFromStakeholder: true,
185
236
  isForceOpen,
186
237
  listOfPolylines: polylinesRef.current,
238
+ stakeholderType: stakeholder.type,
239
+ animated: true,
187
240
  });
188
241
  });
189
242
 
@@ -199,12 +252,67 @@ export default function LocationIcon({
199
252
  rootsMapRef.current.clear();
200
253
  markersRef.current = [];
201
254
  };
202
- }, [stakeholdersOfLocation, selectedMarkersId, activeMarker]);
255
+ }, [stakeholdersOfLocation, selectedMarkersId, activeMarker, zoom]);
203
256
 
204
- linkedNodesData.map((node) => {
205
- const id = `${data.datastakeId}-${node.datastakeId}`;
257
+ // Only create polylines for linked nodes when something is selected
258
+ useEffect(() => {
259
+ if (selectedMarkersId.length === 0) {
260
+ return;
261
+ }
262
+
263
+ // IMPORTANT: Only draw links if this location is actually selected
264
+ // Not just highlighted as part of the chain
265
+ if (!isSelected) {
266
+ return;
267
+ }
268
+
269
+ // Filter linkedNodesData to only include nodes that are in the selected chain
270
+ const relevantLinks = linkedNodesData.filter(node => {
271
+ // Check if the target node (location) is in the selected markers
272
+ const targetLocationInSelection = selectedMarkersId.includes(node.datastakeId);
273
+
274
+ // If connecting to a stakeholder, check if that stakeholder is selected
275
+ if (node.isStakeholder) {
276
+ const stakeholderInSelection = node.stakeholdersIndex !== undefined &&
277
+ selectedMarkersId.includes(node.datastakeId);
278
+ return stakeholderInSelection;
279
+ }
280
+
281
+ return targetLocationInSelection;
282
+ });
283
+
284
+ relevantLinks.forEach((node) => {
285
+ const id = node.fromStakeholderId
286
+ ? `${node.fromStakeholderId}-${node.datastakeId}`
287
+ : `${data.datastakeId}-${node.datastakeId}`;
206
288
  const isConnectingToStakeholder = node.isStakeholder;
207
- const centerLatLng = L.latLng(data.gps.latitude, data.gps.longitude);
289
+
290
+ // If the link is from a stakeholder, start from the stakeholder position
291
+ let startLatLng;
292
+ if (node.fromStakeholderId) {
293
+ // Find the stakeholder index in this location's stakeholders
294
+ const stakeholderIndex = stakeholdersOfLocation.findIndex(
295
+ s => s.datastakeId === node.fromStakeholderId
296
+ );
297
+
298
+ if (stakeholderIndex !== -1) {
299
+ const { x, y } = getStakeholderPosition({
300
+ zoom,
301
+ totalMarkers: stakeholdersOfLocation.length,
302
+ markerIndex: stakeholderIndex,
303
+ });
304
+
305
+ const centerLatLng = L.latLng(data.gps.latitude, data.gps.longitude);
306
+ const centerPoint = mapRef.latLngToLayerPoint(centerLatLng);
307
+ const stakeholderPoint = centerPoint.add(L.point(x, y));
308
+ startLatLng = mapRef.layerPointToLatLng(stakeholderPoint);
309
+ } else {
310
+ startLatLng = L.latLng(data.gps.latitude, data.gps.longitude);
311
+ }
312
+ } else {
313
+ startLatLng = L.latLng(data.gps.latitude, data.gps.longitude);
314
+ }
315
+
208
316
  let endLatLng = L.latLng(node.gps.latitude, node.gps.longitude);
209
317
  const isConnectingToStakeholderSelected = selectedMarkersId.includes(node.datastakeId);
210
318
 
@@ -225,15 +333,15 @@ export default function LocationIcon({
225
333
  createPolyline({
226
334
  L,
227
335
  mapRef,
228
- startLatLng: centerLatLng,
336
+ startLatLng,
229
337
  endLatLng,
230
-
231
338
  isSelected: isConnectingToStakeholderSelected,
232
339
  id,
233
340
  zoom,
234
341
  listOfPolylines: polylinesRef.current,
235
342
  });
236
343
  });
344
+ }, [linkedNodesData, selectedMarkersId, zoom, stakeholdersOfLocation, isSelected]);
237
345
 
238
346
  return (
239
347
  <Popover
@@ -5,13 +5,14 @@ const VILLAGE = "village";
5
5
  const EXPORTER = "exporter";
6
6
  const PROCESSOR = "mineralProcessor";
7
7
  const DEPOT = "depot";
8
+ const OPERATOR = "miningOperator";
8
9
 
9
10
  const MAX_EXTRA_SMALL_ZOOM_THRESHOLD = 2;
10
11
  const MAX_SMALL_ZOOM_THRESHOLD = 3;
11
12
  const MAX_MEDIUM_ZOOM_THRESHOLD = 6;
12
13
 
13
14
  const LOCATION_TYPES = [MINE_SITE, VILLAGE];
14
- const STAKEHOLDER_TYPES = [EXPORTER, PROCESSOR, DEPOT];
15
+ const STAKEHOLDER_TYPES = [EXPORTER, PROCESSOR, DEPOT, OPERATOR];
15
16
 
16
17
  const RADIUS_SMALL = 15;
17
18
  const RADIUS_MEDIUM = 35;
@@ -100,7 +101,6 @@ export function getStakeholderPosition({ zoom, totalMarkers, markerIndex }) {
100
101
 
101
102
  let radius;
102
103
  let center = {
103
- // NOT BEING USED FOR NOW AND MAYBE NEVER
104
104
  left: 0,
105
105
  top: 0,
106
106
  };
@@ -117,6 +117,32 @@ export function getStakeholderPosition({ zoom, totalMarkers, markerIndex }) {
117
117
  return { x, y, center, radius, angleDeg };
118
118
  }
119
119
 
120
+ function applyAnimationDirect(el, isShortLink) {
121
+ if (!(el instanceof SVGElement) || isShortLink) return;
122
+
123
+ el.style.strokeDasharray = "10, 10";
124
+ el.style.strokeDashoffset = "0";
125
+
126
+ el.style.animation = "dash-flow 1.2s linear infinite";
127
+
128
+ el.classList.add('animated-polyline');
129
+ }
130
+
131
+ function removeAnimationFromElement(element) {
132
+ if (!element) return;
133
+
134
+ element.classList.remove('animated-polyline');
135
+ element.style.animation = '';
136
+ element.style.strokeDasharray = '';
137
+ }
138
+
139
+ function applyAnimationToPolyline(polyline, isShortLink) {
140
+ const element = polyline.getElement();
141
+ if (element) {
142
+ applyAnimationDirect(element, isShortLink);
143
+ }
144
+ }
145
+
120
146
  export function createPolyline({
121
147
  L,
122
148
  startLatLng,
@@ -127,32 +153,62 @@ export function createPolyline({
127
153
  listOfPolylines = [],
128
154
  isFromStakeholder = false,
129
155
  isForceOpen = false,
156
+ stakeholderType = null,
157
+ animated = false,
158
+ mapRef
130
159
  }) {
131
- const width = isFromStakeholder && isExtraSmallMarker(zoom) && !isForceOpen ? 0 : 1.2;
160
+ const lineWidth = isFromStakeholder && isExtraSmallMarker(zoom) && !isForceOpen ? 0 : 1.2;
161
+
162
+ const isShortLink = stakeholderType === OPERATOR || isFromStakeholder;
163
+ const shouldAnimate = animated;
132
164
 
133
- const coordinates = [
165
+ const lineCoordinates = [
134
166
  [startLatLng.lat, startLatLng.lng],
135
167
  [endLatLng.lat, endLatLng.lng],
136
168
  ];
137
- const style = {
169
+
170
+ const polylineStyle = {
138
171
  color: "var(--base-gray-70)",
139
- weight: width,
140
- opacity: 0.5,
141
- smoothFactor: 1,
172
+ weight: lineWidth,
173
+ opacity: isSelected ? 1 : 0.5,
174
+ smoothFactor: 0,
142
175
  id,
143
- dashArray: !isSelected ? "5, 5" : "0, 0",
176
+ dashArray: isShortLink ? "0, 0" : (shouldAnimate ? "10, 10" : (!isSelected ? "5, 5" : "10, 10")),
177
+ renderer: L.svg()
144
178
  };
145
179
 
146
- const newPolyline = L.polyline(coordinates, style);
147
-
148
- if (listOfPolylines.find((p) => p.options.id === id)) {
149
- const polylineToUpdateCoordinates = listOfPolylines.find((p) => p.options.id === id);
150
- polylineToUpdateCoordinates.setLatLngs(coordinates);
151
- polylineToUpdateCoordinates.setStyle(style);
152
- } else {
153
- listOfPolylines.push(newPolyline);
180
+ const existingPolyline = listOfPolylines.find(p => p.options.id === id);
181
+
182
+ if (existingPolyline) {
183
+ removeAnimationFromElement(existingPolyline.getElement());
184
+
185
+ existingPolyline.setLatLngs(lineCoordinates);
186
+ existingPolyline.setStyle(polylineStyle);
187
+
188
+ if (shouldAnimate && isSelected) {
189
+ existingPolyline.once('add', () => {
190
+ applyAnimationToPolyline(existingPolyline, isShortLink);
191
+ });
192
+
193
+ applyAnimationToPolyline(existingPolyline, isShortLink);
194
+ }
195
+
196
+ return existingPolyline;
154
197
  }
155
198
 
199
+ const newPolyline = L.polyline(lineCoordinates, polylineStyle);
200
+
201
+ newPolyline.addTo(mapRef);
202
+ listOfPolylines.push(newPolyline);
203
+
204
+ if (shouldAnimate && isSelected) {
205
+ newPolyline.once('add', () => {
206
+ applyAnimationToPolyline(newPolyline, isShortLink);
207
+ });
208
+
209
+ applyAnimationToPolyline(newPolyline, isShortLink);
210
+ }
211
+
156
212
  return newPolyline;
157
213
  }
158
214
 
@@ -369,6 +369,7 @@ export function useMapHelper({
369
369
  onClickLink={onClickLink}
370
370
  activeStakeholder={activeStakeholder}
371
371
  setActiveStakeholder={setActiveStakeholder}
372
+ mapRef={mapRef}
372
373
  />,
373
374
  );
374
375
 
@@ -31,7 +31,7 @@ export const useMap = ({
31
31
  const container = createRef();
32
32
  const [data, setData] = useState([]);
33
33
  const [mapRef, setMapRef] = useState(null);
34
- const { TILE_LAYER_URL, MAP_TOKEN } = useMapConfig({ app, isSatellite });
34
+ const { TILE_LAYER_URL, MAP_TOKEN } = useMapConfig({ app, isSatellite, mapRef: container });
35
35
  const [initialMarkerSetIsDone, setInitialMarkerSetIsDone] = useState(false);
36
36
  const [mapCenter, setMapCenter] = useState([0, 0]);
37
37
  const [emptyStateIsVisible, setEmptyStateIsVisible] = useState(false);
@@ -48,6 +48,7 @@ export const useMap = ({
48
48
  const stakeToLoc = new Map();
49
49
  const nodeTypes = new Map();
50
50
 
51
+ // Build the graph
51
52
  for (const loc of data) {
52
53
  const locId = loc.datastakeId;
53
54
  nodeTypes.set(locId, loc.type);
@@ -81,34 +82,45 @@ export const useMap = ({
81
82
 
82
83
  const highlightTable = {};
83
84
 
85
+ // Perform BFS/DFS to find all connected nodes in the entire chain
84
86
  for (const [node] of graph) {
85
87
  const highlighted = new Set();
88
+ const queue = [node];
89
+ const visited = new Set([node]);
86
90
 
87
- highlighted.add(node);
88
-
89
- const nodeIsStakeholder = !isLocation(nodeTypes.get(node));
90
- if (nodeIsStakeholder && stakeToLoc.has(node)) {
91
- const parentLoc = stakeToLoc.get(node);
92
- highlighted.add(parentLoc);
93
- }
94
-
95
- for (const neighbor of graph.get(node) || []) {
96
- const neighborIsStakeholder = !isLocation(nodeTypes.get(neighbor));
91
+ while (queue.length > 0) {
92
+ const current = queue.shift();
93
+ highlighted.add(current);
97
94
 
98
- if (neighborIsStakeholder && stakeToLoc.has(neighbor)) {
99
- const neighborParent = stakeToLoc.get(neighbor);
100
-
101
- if (
102
- (isLocation(nodeTypes.get(node)) && neighborParent === node) ||
103
- (nodeIsStakeholder && stakeToLoc.get(node) === neighborParent)
104
- ) {
105
- highlighted.add(neighbor);
106
- } else {
95
+ // Add parent location if current is stakeholder
96
+ const currentIsStakeholder = !isLocation(nodeTypes.get(current));
97
+ if (currentIsStakeholder && stakeToLoc.has(current)) {
98
+ const parentLoc = stakeToLoc.get(current);
99
+ if (!visited.has(parentLoc)) {
100
+ highlighted.add(parentLoc);
101
+ visited.add(parentLoc);
102
+ queue.push(parentLoc);
103
+ }
104
+ }
105
+
106
+ // Traverse all neighbors
107
+ for (const neighbor of graph.get(current) || []) {
108
+ if (!visited.has(neighbor)) {
109
+ visited.add(neighbor);
110
+ queue.push(neighbor);
107
111
  highlighted.add(neighbor);
108
- highlighted.add(neighborParent);
112
+
113
+ // If neighbor is stakeholder, add its parent location
114
+ const neighborIsStakeholder = !isLocation(nodeTypes.get(neighbor));
115
+ if (neighborIsStakeholder && stakeToLoc.has(neighbor)) {
116
+ const neighborParent = stakeToLoc.get(neighbor);
117
+ if (!visited.has(neighborParent)) {
118
+ highlighted.add(neighborParent);
119
+ visited.add(neighborParent);
120
+ queue.push(neighborParent);
121
+ }
122
+ }
109
123
  }
110
- } else {
111
- highlighted.add(neighbor);
112
124
  }
113
125
  }
114
126
 
@@ -151,10 +163,21 @@ export const useMap = ({
151
163
  destroyAllPopovers();
152
164
  setSelectedMarkersId((prev) => {
153
165
  if (prev.includes(clickedMarker.datastakeId)) {
166
+ // Deselecting - clear polylines
154
167
  openPopupIdRef.current = null;
155
168
  setMarkerWithPopup(null);
156
169
  return [];
157
170
  } else {
171
+ // CLEAR OLD POLYLINES BEFORE SELECTING NEW MARKER
172
+ if (polylinesRef.current.length > 0) {
173
+ polylinesRef.current.forEach((polyline) => {
174
+ if (mapRef.hasLayer(polyline)) {
175
+ mapRef.removeLayer(polyline);
176
+ }
177
+ });
178
+ polylinesRef.current = [];
179
+ }
180
+
158
181
  setMarkerWithPopup(isStakeholder(clickedMarker.type) ? clickedMarker : null);
159
182
  const newSelectedMarkersId = highlightTable[clickedMarker.datastakeId];
160
183
  openPopupIdRef.current = clickedMarker.datastakeId;
@@ -191,12 +214,22 @@ export const useMap = ({
191
214
  });
192
215
  }
193
216
  }
217
+
218
+ if (type === "chain" && selectedMarkersId.length === 0) {
219
+ if (polylinesRef.current.length) {
220
+ polylinesRef.current.forEach((polyline) => {
221
+ if (mapRef.hasLayer(polyline)) {
222
+ mapRef.removeLayer(polyline);
223
+ }
224
+ });
225
+ polylinesRef.current = [];
226
+ }
227
+ }
228
+
194
229
  clearMapMarkers();
195
230
  if (data) {
196
- // Filters out locations that are not connected to any stakeholders
197
- const excludedType = ['village', 'town', 'area', 'territory']
198
231
  const filteredData = data?.filter((obj) =>
199
- !excludedType.includes(obj?.type) && (obj?.stakeholders?.length > 0 ||
232
+ obj.type === 'mineSite' || (obj?.stakeholders?.length > 0 ||
200
233
  data.some((other) =>
201
234
  other.datastakeId !== obj.datastakeId &&
202
235
  (other.stakeholders || []).some((stk) =>
@@ -218,9 +251,11 @@ export const useMap = ({
218
251
  );
219
252
  });
220
253
 
221
- polylinesRef.current.forEach((polyline) => {
222
- mapRef.addLayer(polyline);
223
- });
254
+ if (selectedMarkersId.length > 0) {
255
+ polylinesRef.current.forEach((polyline) => {
256
+ mapRef.addLayer(polyline);
257
+ });
258
+ }
224
259
 
225
260
  mapRef.invalidateSize();
226
261
  mapRef.fire("moveend");
@@ -7,6 +7,8 @@ const Style = styled.div`
7
7
  width: 100%;
8
8
  height: 472px;
9
9
 
10
+
11
+
10
12
  .filter-cont {
11
13
  position: absolute;
12
14
  top: 24px;
@@ -109,11 +111,24 @@ const Style = styled.div`
109
111
  align-items: center;
110
112
  }
111
113
 
112
- .marker-chain {
113
- display: flex;
114
- align-items: center;
115
- justify-content: center;
116
- }
114
+ .marker-chain {
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ }
119
+
120
+ .animated-polyline {
121
+ stroke-dasharray: 10 10;
122
+ animation: dash-flow 1.5s linear infinite;
123
+ stroke-linecap: round;
124
+ }
125
+
126
+ @keyframes dash-flow {
127
+ to {
128
+ stroke-dashoffset: -20;
129
+ }
130
+ }
131
+
117
132
 
118
133
  }
119
134
 
@@ -82,7 +82,7 @@ const CycleOutcomes = ({
82
82
  <section style={{ flex: 1 }}>
83
83
  <StatCard
84
84
  title={t("Total Area Restored")}
85
- value={totalAreaRestored?Number(totalAreaRestored.current).toLocaleString() : 0}
85
+ value={totalAreaRestored?Number(totalAreaRestored.current).toLocaleString() + " ha" : 0 + " ha"}
86
86
  icon="Tree"
87
87
  change={totalAreaRestoredChange}
88
88
  />