datastake-daf 0.6.773 → 0.6.775

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 (27) hide show
  1. package/dist/components/index.js +2656 -2476
  2. package/dist/hooks/index.js +72 -0
  3. package/dist/pages/index.js +1211 -949
  4. package/dist/utils/index.js +13 -0
  5. package/package.json +1 -1
  6. package/src/@daf/core/components/Dashboard/Map/ChainIcon/Markers/StakeholderMarker.js +8 -76
  7. package/src/@daf/core/components/Dashboard/Map/ChainIcon/index.js +116 -8
  8. package/src/@daf/core/components/Dashboard/Map/ChainIcon/utils.js +73 -17
  9. package/src/@daf/core/components/Dashboard/Map/helper.js +1 -0
  10. package/src/@daf/core/components/Dashboard/Map/hook.js +53 -29
  11. package/src/@daf/core/components/Dashboard/Map/style.js +20 -5
  12. package/src/@daf/hooks/useTimeFilter.js +56 -0
  13. package/src/@daf/hooks/useViewFormUrlParams.js +84 -0
  14. package/src/@daf/pages/Summary/Activities/PlantingCycle/components/AssociatedInformation/config.js +7 -13
  15. package/src/@daf/pages/Summary/Activities/PlantingCycle/components/AssociatedInformation/index.jsx +3 -1
  16. package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CommunityParticipation/JobsTimeline/index.jsx +33 -101
  17. package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CycleIndicators/HealthAndSafety/helper.js +8 -6
  18. package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CycleIndicators/HealthAndSafety/index.jsx +73 -4
  19. package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CycleIndicators/index.jsx +1 -1
  20. package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CycleOutcomes/PlantingActivitiesTimeline.jsx +148 -0
  21. package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CycleOutcomes/RestoredArea.jsx +150 -0
  22. package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CycleOutcomes/index.jsx +11 -390
  23. package/src/@daf/pages/Summary/Activities/PlantingCycle/index.jsx +2 -2
  24. package/src/@daf/utils/object.js +3 -1
  25. package/src/@daf/utils/timeFilterUtils.js +226 -0
  26. package/src/hooks.js +2 -1
  27. package/src/utils.js +1 -1
@@ -7570,6 +7570,8 @@ const Style$f = styled__default["default"].div`
7570
7570
  width: 100%;
7571
7571
  height: 472px;
7572
7572
 
7573
+
7574
+
7573
7575
  .filter-cont {
7574
7576
  position: absolute;
7575
7577
  top: 24px;
@@ -7672,11 +7674,24 @@ const Style$f = styled__default["default"].div`
7672
7674
  align-items: center;
7673
7675
  }
7674
7676
 
7675
- .marker-chain {
7676
- display: flex;
7677
- align-items: center;
7678
- justify-content: center;
7679
- }
7677
+ .marker-chain {
7678
+ display: flex;
7679
+ align-items: center;
7680
+ justify-content: center;
7681
+ }
7682
+
7683
+ .animated-polyline {
7684
+ stroke-dasharray: 10 10;
7685
+ animation: dash-flow 1.5s linear infinite;
7686
+ stroke-linecap: round;
7687
+ }
7688
+
7689
+ @keyframes dash-flow {
7690
+ to {
7691
+ stroke-dashoffset: -20;
7692
+ }
7693
+ }
7694
+
7680
7695
 
7681
7696
  }
7682
7697
 
@@ -8200,18 +8215,15 @@ const VILLAGE = "village";
8200
8215
  const EXPORTER = "exporter";
8201
8216
  const PROCESSOR = "mineralProcessor";
8202
8217
  const DEPOT = "depot";
8218
+ const OPERATOR = "miningOperator";
8203
8219
  const MAX_EXTRA_SMALL_ZOOM_THRESHOLD = 2;
8204
8220
  const MAX_SMALL_ZOOM_THRESHOLD = 3;
8205
8221
  const MAX_MEDIUM_ZOOM_THRESHOLD = 6;
8206
8222
  const LOCATION_TYPES = [MINE_SITE, VILLAGE];
8207
- const STAKEHOLDER_TYPES = [EXPORTER, PROCESSOR, DEPOT];
8223
+ const STAKEHOLDER_TYPES = [EXPORTER, PROCESSOR, DEPOT, OPERATOR];
8208
8224
  const RADIUS_SMALL = 15;
8209
8225
  const RADIUS_MEDIUM = 35;
8210
8226
  const RADIUS_LARGE = 60;
8211
- const RADIUS_CURVE_SMALL = 10;
8212
- const RADIUS_CURVE_MEDIUM = 15;
8213
- const RADIUS_CURVE_LARGE = 20;
8214
- const TENSION = 0.2;
8215
8227
  function isLocation(type) {
8216
8228
  return LOCATION_TYPES.includes(type);
8217
8229
  }
@@ -8268,7 +8280,6 @@ function getStakeholderPosition({
8268
8280
  const isLarge = isLargeMarker(zoom);
8269
8281
  let radius;
8270
8282
  let center = {
8271
- // NOT BEING USED FOR NOW AND MAYBE NEVER
8272
8283
  left: 0,
8273
8284
  top: 0
8274
8285
  };
@@ -8292,6 +8303,25 @@ function getStakeholderPosition({
8292
8303
  angleDeg
8293
8304
  };
8294
8305
  }
8306
+ function applyAnimationDirect(el, isShortLink) {
8307
+ if (!(el instanceof SVGElement) || isShortLink) return;
8308
+ el.style.strokeDasharray = "10, 10";
8309
+ el.style.strokeDashoffset = "0";
8310
+ el.style.animation = "dash-flow 1.2s linear infinite";
8311
+ el.classList.add('animated-polyline');
8312
+ }
8313
+ function removeAnimationFromElement(element) {
8314
+ if (!element) return;
8315
+ element.classList.remove('animated-polyline');
8316
+ element.style.animation = '';
8317
+ element.style.strokeDasharray = '';
8318
+ }
8319
+ function applyAnimationToPolyline(polyline, isShortLink) {
8320
+ const element = polyline.getElement();
8321
+ if (element) {
8322
+ applyAnimationDirect(element, isShortLink);
8323
+ }
8324
+ }
8295
8325
  function createPolyline({
8296
8326
  L,
8297
8327
  startLatLng,
@@ -8301,109 +8331,47 @@ function createPolyline({
8301
8331
  zoom,
8302
8332
  listOfPolylines = [],
8303
8333
  isFromStakeholder = false,
8304
- isForceOpen = false
8334
+ isForceOpen = false,
8335
+ stakeholderType = null,
8336
+ animated = false,
8337
+ mapRef
8305
8338
  }) {
8306
- const width = isFromStakeholder && isExtraSmallMarker(zoom) && !isForceOpen ? 0 : 1.2;
8307
- const coordinates = [[startLatLng.lat, startLatLng.lng], [endLatLng.lat, endLatLng.lng]];
8308
- const style = {
8339
+ const lineWidth = isFromStakeholder && isExtraSmallMarker(zoom) && !isForceOpen ? 0 : 1.2;
8340
+ const isShortLink = stakeholderType === OPERATOR || isFromStakeholder;
8341
+ const shouldAnimate = animated;
8342
+ const lineCoordinates = [[startLatLng.lat, startLatLng.lng], [endLatLng.lat, endLatLng.lng]];
8343
+ const polylineStyle = {
8309
8344
  color: "var(--base-gray-70)",
8310
- weight: width,
8311
- opacity: 0.5,
8312
- smoothFactor: 1,
8345
+ weight: lineWidth,
8346
+ opacity: isSelected ? 1 : 0.5,
8347
+ smoothFactor: 0,
8313
8348
  id,
8314
- dashArray: !isSelected ? "5, 5" : "0, 0"
8349
+ dashArray: isShortLink ? "0, 0" : shouldAnimate ? "10, 10" : !isSelected ? "5, 5" : "10, 10",
8350
+ renderer: L.svg()
8315
8351
  };
8316
- const newPolyline = L.polyline(coordinates, style);
8317
- if (listOfPolylines.find(p => p.options.id === id)) {
8318
- const polylineToUpdateCoordinates = listOfPolylines.find(p => p.options.id === id);
8319
- polylineToUpdateCoordinates.setLatLngs(coordinates);
8320
- polylineToUpdateCoordinates.setStyle(style);
8321
- } else {
8322
- listOfPolylines.push(newPolyline);
8352
+ const existingPolyline = listOfPolylines.find(p => p.options.id === id);
8353
+ if (existingPolyline) {
8354
+ removeAnimationFromElement(existingPolyline.getElement());
8355
+ existingPolyline.setLatLngs(lineCoordinates);
8356
+ existingPolyline.setStyle(polylineStyle);
8357
+ if (shouldAnimate && isSelected) {
8358
+ existingPolyline.once('add', () => {
8359
+ applyAnimationToPolyline(existingPolyline, isShortLink);
8360
+ });
8361
+ applyAnimationToPolyline(existingPolyline, isShortLink);
8362
+ }
8363
+ return existingPolyline;
8323
8364
  }
8324
- return newPolyline;
8325
- }
8326
- function createCurvePath({
8327
- zoom,
8328
- totalMarkers,
8329
- markerIndex
8330
- }) {
8331
- const radius = getCurvePointRadius(zoom);
8332
- const {
8333
- x,
8334
- y,
8335
- angleDeg
8336
- } = getAngleDeg(totalMarkers, markerIndex, radius);
8337
- return {
8338
- x,
8339
- y,
8340
- angleDeg
8341
- };
8342
- }
8343
- function getCurvePointRadius(zoom) {
8344
- const isSmall = isSmallMarker(zoom) || isExtraSmallMarker(zoom);
8345
- const isMedium = isMediumMarker(zoom);
8346
- if (isSmall) {
8347
- return RADIUS_SMALL + RADIUS_CURVE_SMALL;
8348
- } else if (isMedium) {
8349
- return RADIUS_MEDIUM + RADIUS_CURVE_MEDIUM;
8350
- } else {
8351
- return RADIUS_LARGE + RADIUS_CURVE_LARGE;
8365
+ const newPolyline = L.polyline(lineCoordinates, polylineStyle);
8366
+ newPolyline.addTo(mapRef);
8367
+ listOfPolylines.push(newPolyline);
8368
+ if (shouldAnimate && isSelected) {
8369
+ newPolyline.once('add', () => {
8370
+ applyAnimationToPolyline(newPolyline, isShortLink);
8371
+ });
8372
+ applyAnimationToPolyline(newPolyline, isShortLink);
8352
8373
  }
8353
- }
8354
- function buildSmoothCurve(layerPoints, mapRef) {
8355
- const path = [];
8356
- for (let i = 0; i < layerPoints.length - 1; i++) {
8357
- const p0 = layerPoints[i];
8358
- const p1 = layerPoints[i + 1];
8359
- const pPrev = layerPoints[i - 1] || p0;
8360
- const pNext = layerPoints[i + 2] || p1;
8361
- const cp1 = L__namespace.point(p0.x + (p1.x - pPrev.x) * TENSION, p0.y + (p1.y - pPrev.y) * TENSION);
8362
- const cp2 = L__namespace.point(p1.x - (pNext.x - p0.x) * TENSION, p1.y - (pNext.y - p0.y) * TENSION);
8363
- if (i === 0) {
8364
- path.push("M", [mapRef.layerPointToLatLng(p0).lat, mapRef.layerPointToLatLng(p0).lng]);
8365
- }
8366
- path.push("C", [mapRef.layerPointToLatLng(cp1).lat, mapRef.layerPointToLatLng(cp1).lng], [mapRef.layerPointToLatLng(cp2).lat, mapRef.layerPointToLatLng(cp2).lng], [mapRef.layerPointToLatLng(p1).lat, mapRef.layerPointToLatLng(p1).lng]);
8367
- }
8368
- return path;
8369
- }
8370
- function getSiblingCurveStrength(zoom) {
8371
- if (isExtraSmallMarker(zoom)) return RADIUS_CURVE_SMALL / 2;
8372
- if (isSmallMarker(zoom)) return RADIUS_CURVE_MEDIUM;
8373
- if (isMediumMarker(zoom)) return RADIUS_CURVE_LARGE;
8374
- return RADIUS_CURVE_LARGE;
8375
- }
8376
- function buildCurveWIthTwoSiblings({
8377
- mapRef,
8378
- startLatLng,
8379
- endLatLng,
8380
- zoom,
8381
- isSelected,
8382
- id
8383
- }) {
8384
- const fromPoint = mapRef.latLngToLayerPoint(startLatLng);
8385
- const toPoint = mapRef.latLngToLayerPoint(endLatLng);
8386
- const midX = (fromPoint.x + toPoint.x) / 2;
8387
- const midY = (fromPoint.y + toPoint.y) / 2 + (isSmallMarker(zoom) ? RADIUS_CURVE_SMALL / 2 : 0);
8388
- const dx = toPoint.x - fromPoint.x;
8389
- const dy = toPoint.y - fromPoint.y;
8390
- const normal = L__namespace.point(-dy, dx);
8391
- const length = Math.sqrt(normal.x ** 2 + normal.y ** 2) || 1;
8392
- const normalized = normal.multiplyBy(1 / length);
8393
- const curveStrength = getSiblingCurveStrength(zoom);
8394
- const controlPoint = L__namespace.point(midX, midY).add(normalized.multiplyBy(curveStrength));
8395
- const latlngs = [startLatLng, mapRef.layerPointToLatLng(controlPoint), endLatLng];
8396
- const layerPoints = latlngs.map(latlng => mapRef.latLngToLayerPoint(latlng));
8397
- const path = buildSmoothCurve(layerPoints, mapRef);
8398
- const curve = L__namespace.curve(path, {
8399
- color: "var(--base-gray-70)",
8400
- weight: isExtraSmallMarker(zoom) ? 0 : 1.2,
8401
- opacity: 0.5,
8402
- smoothFactor: 1,
8403
- id,
8404
- dashArray: !isSelected ? "5, 5" : "0, 0"
8405
- });
8406
- mapRef.addLayer(curve);
8374
+ return newPolyline;
8407
8375
  }
8408
8376
 
8409
8377
  const StakeholderMarker = styled__default["default"].div`
@@ -8705,6 +8673,9 @@ function StakeholderIcon$1({
8705
8673
  return null;
8706
8674
  }, [parentId, allData]);
8707
8675
  React.useEffect(() => {
8676
+ if (selectedMarkersId.length === 0 || !isSelected) {
8677
+ return;
8678
+ }
8708
8679
  linkNodesData.map(node => {
8709
8680
  const isConnectingToStakeholder = node.isStakeholder;
8710
8681
  const id = `${data.datastakeId}-${node.stakeholderId || node.datastakeId}`;
@@ -8716,8 +8687,6 @@ function StakeholderIcon$1({
8716
8687
  const stakeholderPoint = centerPoint.add(L__namespace.point(x, y));
8717
8688
  const stakeholderLatLng = mapRef.layerPointToLatLng(stakeholderPoint);
8718
8689
  let endLatLng = L__namespace.latLng(node.gps.latitude, node.gps.longitude);
8719
- const areNextToEachOther = targetMarkerIndex === index + 1 || targetMarkerIndex === index - 1 || index === 0 && targetMarkerIndex === node.totalStakeholders - 1 || targetMarkerIndex === 0 && index === node.totalStakeholders - 1;
8720
- const areOnlyTwoSiblings = node.totalStakeholders === 2;
8721
8690
  if (isExtraSmallMarker(zoom) && !isForceOpen) {
8722
8691
  createPolyline({
8723
8692
  L: L__namespace,
@@ -8727,7 +8696,8 @@ function StakeholderIcon$1({
8727
8696
  zoom,
8728
8697
  isSelected,
8729
8698
  id,
8730
- listOfPolylines: polylinesRef.current
8699
+ listOfPolylines: polylinesRef.current,
8700
+ animated: true
8731
8701
  });
8732
8702
  return;
8733
8703
  }
@@ -8745,61 +8715,8 @@ function StakeholderIcon$1({
8745
8715
  const nodePoint = mapRef.latLngToLayerPoint(nodeLatLng);
8746
8716
  const endPoint = L__namespace.point(x + nodePoint.x + center.left, y + nodePoint.y + center.top);
8747
8717
  endLatLng = mapRef.layerPointToLatLng(endPoint);
8748
- if (isSibling && (!areNextToEachOther || areOnlyTwoSiblings)) {
8749
- if (areOnlyTwoSiblings) {
8750
- buildCurveWIthTwoSiblings({
8751
- mapRef,
8752
- startLatLng: stakeholderLatLng,
8753
- endLatLng,
8754
- zoom,
8755
- isSelected,
8756
- id
8757
- });
8758
- return;
8759
- }
8760
- const total = node.totalStakeholders;
8761
- let from = index;
8762
- let to = targetMarkerIndex;
8763
- let flip = false;
8764
- const forwardDistance = (to - from + total) % total;
8765
- const backwardDistance = (from - to + total) % total;
8766
- if (backwardDistance < forwardDistance) {
8767
- [from, to] = [to, from];
8768
- flip = true;
8769
- }
8770
- const intermediateIndices = [];
8771
- for (let i = 1; i < (to - from + total) % total; i++) {
8772
- intermediateIndices.push((from + i) % total);
8773
- }
8774
- const indices = [from, ...intermediateIndices, to];
8775
- const intermediatePoints = [];
8776
- for (const i of indices) {
8777
- const {
8778
- x,
8779
- y
8780
- } = createCurvePath({
8781
- zoom,
8782
- totalMarkers: node.totalStakeholders,
8783
- markerIndex: i
8784
- });
8785
- const point = centerPoint.add(L__namespace.point(x, y));
8786
- const latlng = mapRef.layerPointToLatLng(point);
8787
- intermediatePoints.push(latlng);
8788
- }
8789
- const latlngs = flip ? [endLatLng, ...intermediatePoints, stakeholderLatLng] : [stakeholderLatLng, ...intermediatePoints, endLatLng];
8790
- const layerPoints = latlngs.map(latlng => mapRef.latLngToLayerPoint(latlng));
8791
- const path = buildSmoothCurve(layerPoints, mapRef);
8792
- const curve = L__namespace?.curve?.(path, {
8793
- color: "var(--base-gray-70)",
8794
- weight: isExtraSmallMarker(zoom) ? 0 : 1,
8795
- opacity: isSelected ? 1 : 0.5,
8796
- smoothFactor: 1,
8797
- id
8798
- });
8799
- mapRef.addLayer(curve);
8800
- return;
8801
- }
8802
8718
  }
8719
+ // Always use straight lines
8803
8720
  createPolyline({
8804
8721
  L: L__namespace,
8805
8722
  mapRef,
@@ -8809,10 +8726,11 @@ function StakeholderIcon$1({
8809
8726
  isFromStakeholder: false,
8810
8727
  isSelected,
8811
8728
  id,
8812
- listOfPolylines: polylinesRef.current
8729
+ listOfPolylines: polylinesRef.current,
8730
+ animated: true
8813
8731
  });
8814
8732
  });
8815
- }, [mapRef, x, y, parentData, linkNodesData, isSelected, zoom, isForceOpen]);
8733
+ }, [mapRef, x, y, parentData, linkNodesData, isSelected, zoom, isForceOpen, selectedMarkersId]);
8816
8734
  return /*#__PURE__*/jsxRuntime.jsx(jsxRuntime.Fragment, {
8817
8735
  children: /*#__PURE__*/jsxRuntime.jsx(antd.Popover, {
8818
8736
  content: renderTooltipJsx({
@@ -8879,6 +8797,8 @@ function LocationIcon({
8879
8797
  const linkedNodesData = React.useMemo(() => {
8880
8798
  const nodes = [];
8881
8799
  const links = data.links || [];
8800
+
8801
+ // Add links from the location itself
8882
8802
  links.forEach(link => {
8883
8803
  allData.forEach(d => {
8884
8804
  if (d.datastakeId === link) {
@@ -8898,8 +8818,45 @@ function LocationIcon({
8898
8818
  }
8899
8819
  });
8900
8820
  });
8821
+
8822
+ // ADD: Also include links from this location's stakeholders
8823
+ const stakeholders = data.stakeholders || [];
8824
+ stakeholders.forEach(stakeholder => {
8825
+ const stakeholderLinks = stakeholder.links || [];
8826
+ stakeholderLinks.forEach(link => {
8827
+ allData.forEach(d => {
8828
+ // Check if it's a direct location link
8829
+ if (d.datastakeId === link) {
8830
+ // Avoid duplicates
8831
+ if (!nodes.find(n => n.datastakeId === link && !n.isStakeholder)) {
8832
+ nodes.push({
8833
+ ...d,
8834
+ fromStakeholderId: stakeholder.datastakeId
8835
+ });
8836
+ }
8837
+ }
8838
+ // Check if it's a stakeholder link
8839
+ if (d.stakeholders && d.stakeholders.length > 0) {
8840
+ d.stakeholders.forEach(targetStakeholder => {
8841
+ if (targetStakeholder.datastakeId === link) {
8842
+ // Avoid duplicates
8843
+ if (!nodes.find(n => n.isStakeholder && n.datastakeId === d.datastakeId && n.stakeholdersIndex === d.stakeholders.indexOf(targetStakeholder))) {
8844
+ nodes.push({
8845
+ ...d,
8846
+ isStakeholder: true,
8847
+ totalStakeholders: d.stakeholders.length,
8848
+ stakeholdersIndex: d.stakeholders.indexOf(targetStakeholder),
8849
+ fromStakeholderId: stakeholder.datastakeId
8850
+ });
8851
+ }
8852
+ }
8853
+ });
8854
+ }
8855
+ });
8856
+ });
8857
+ });
8901
8858
  return nodes;
8902
- }, [JSON.stringify(allData), JSON.stringify(data.links), zoom]);
8859
+ }, [JSON.stringify(allData), JSON.stringify(data.links), JSON.stringify(data.stakeholders), zoom]);
8903
8860
  const stakeholdersOfLocation = React.useMemo(() => {
8904
8861
  return data?.stakeholders || [];
8905
8862
  }, [data.stakeholders, zoom]);
@@ -8917,7 +8874,13 @@ function LocationIcon({
8917
8874
  currentRoots.clear();
8918
8875
  markersRef.current = [];
8919
8876
 
8920
- // Create new markers
8877
+ // Only create stakeholder markers if this location or any of its stakeholders are selected
8878
+ const shouldShowStakeholders = isSelected || stakeholdersOfLocation.some(stk => selectedMarkersId.includes(stk.datastakeId));
8879
+ if (!shouldShowStakeholders || selectedMarkersId.length === 0) {
8880
+ return;
8881
+ }
8882
+
8883
+ // Create new markers only when selected
8921
8884
  stakeholdersOfLocation.forEach((stakeholder, index) => {
8922
8885
  const markerId = `${stakeholder.datastakeId}`;
8923
8886
  const {
@@ -9003,7 +8966,9 @@ function LocationIcon({
9003
8966
  zoom,
9004
8967
  isFromStakeholder: true,
9005
8968
  isForceOpen,
9006
- listOfPolylines: polylinesRef.current
8969
+ listOfPolylines: polylinesRef.current,
8970
+ stakeholderType: stakeholder.type,
8971
+ animated: true
9007
8972
  });
9008
8973
  });
9009
8974
  return () => {
@@ -9018,38 +8983,88 @@ function LocationIcon({
9018
8983
  rootsMapRef.current.clear();
9019
8984
  markersRef.current = [];
9020
8985
  };
9021
- }, [stakeholdersOfLocation, selectedMarkersId, activeMarker]);
9022
- linkedNodesData.map(node => {
9023
- const id = `${data.datastakeId}-${node.datastakeId}`;
9024
- const isConnectingToStakeholder = node.isStakeholder;
9025
- const centerLatLng = L__namespace.latLng(data.gps.latitude, data.gps.longitude);
9026
- let endLatLng = L__namespace.latLng(node.gps.latitude, node.gps.longitude);
9027
- const isConnectingToStakeholderSelected = selectedMarkersId.includes(node.datastakeId);
9028
- if (isConnectingToStakeholder && !isExtraSmallMarker(zoom)) {
9029
- const {
9030
- x,
9031
- y
9032
- } = getStakeholderPosition({
8986
+ }, [stakeholdersOfLocation, selectedMarkersId, activeMarker, zoom]);
8987
+
8988
+ // Only create polylines for linked nodes when something is selected
8989
+ React.useEffect(() => {
8990
+ if (selectedMarkersId.length === 0) {
8991
+ return;
8992
+ }
8993
+
8994
+ // IMPORTANT: Only draw links if this location is actually selected
8995
+ // Not just highlighted as part of the chain
8996
+ if (!isSelected) {
8997
+ return;
8998
+ }
8999
+
9000
+ // Filter linkedNodesData to only include nodes that are in the selected chain
9001
+ const relevantLinks = linkedNodesData.filter(node => {
9002
+ // Check if the target node (location) is in the selected markers
9003
+ const targetLocationInSelection = selectedMarkersId.includes(node.datastakeId);
9004
+
9005
+ // If connecting to a stakeholder, check if that stakeholder is selected
9006
+ if (node.isStakeholder) {
9007
+ const stakeholderInSelection = node.stakeholdersIndex !== undefined && selectedMarkersId.includes(node.datastakeId);
9008
+ return stakeholderInSelection;
9009
+ }
9010
+ return targetLocationInSelection;
9011
+ });
9012
+ relevantLinks.forEach(node => {
9013
+ const id = node.fromStakeholderId ? `${node.fromStakeholderId}-${node.datastakeId}` : `${data.datastakeId}-${node.datastakeId}`;
9014
+ const isConnectingToStakeholder = node.isStakeholder;
9015
+
9016
+ // If the link is from a stakeholder, start from the stakeholder position
9017
+ let startLatLng;
9018
+ if (node.fromStakeholderId) {
9019
+ // Find the stakeholder index in this location's stakeholders
9020
+ const stakeholderIndex = stakeholdersOfLocation.findIndex(s => s.datastakeId === node.fromStakeholderId);
9021
+ if (stakeholderIndex !== -1) {
9022
+ const {
9023
+ x,
9024
+ y
9025
+ } = getStakeholderPosition({
9026
+ zoom,
9027
+ totalMarkers: stakeholdersOfLocation.length,
9028
+ markerIndex: stakeholderIndex
9029
+ });
9030
+ const centerLatLng = L__namespace.latLng(data.gps.latitude, data.gps.longitude);
9031
+ const centerPoint = mapRef.latLngToLayerPoint(centerLatLng);
9032
+ const stakeholderPoint = centerPoint.add(L__namespace.point(x, y));
9033
+ startLatLng = mapRef.layerPointToLatLng(stakeholderPoint);
9034
+ } else {
9035
+ startLatLng = L__namespace.latLng(data.gps.latitude, data.gps.longitude);
9036
+ }
9037
+ } else {
9038
+ startLatLng = L__namespace.latLng(data.gps.latitude, data.gps.longitude);
9039
+ }
9040
+ let endLatLng = L__namespace.latLng(node.gps.latitude, node.gps.longitude);
9041
+ const isConnectingToStakeholderSelected = selectedMarkersId.includes(node.datastakeId);
9042
+ if (isConnectingToStakeholder && !isExtraSmallMarker(zoom)) {
9043
+ const {
9044
+ x,
9045
+ y
9046
+ } = getStakeholderPosition({
9047
+ zoom,
9048
+ totalMarkers: node.totalStakeholders,
9049
+ markerIndex: node.stakeholdersIndex
9050
+ });
9051
+ const nodeLatLng = L__namespace.latLng(node.gps.latitude, node.gps.longitude);
9052
+ const nodePoint = mapRef.latLngToLayerPoint(nodeLatLng);
9053
+ const endPoint = L__namespace.point(x + nodePoint.x, y + nodePoint.y);
9054
+ endLatLng = mapRef.layerPointToLatLng(endPoint);
9055
+ }
9056
+ createPolyline({
9057
+ L: L__namespace,
9058
+ mapRef,
9059
+ startLatLng,
9060
+ endLatLng,
9061
+ isSelected: isConnectingToStakeholderSelected,
9062
+ id,
9033
9063
  zoom,
9034
- totalMarkers: node.totalStakeholders,
9035
- markerIndex: node.stakeholdersIndex
9064
+ listOfPolylines: polylinesRef.current
9036
9065
  });
9037
- const nodeLatLng = L__namespace.latLng(node.gps.latitude, node.gps.longitude);
9038
- const nodePoint = mapRef.latLngToLayerPoint(nodeLatLng);
9039
- const endPoint = L__namespace.point(x + nodePoint.x, y + nodePoint.y);
9040
- endLatLng = mapRef.layerPointToLatLng(endPoint);
9041
- }
9042
- createPolyline({
9043
- L: L__namespace,
9044
- mapRef,
9045
- startLatLng: centerLatLng,
9046
- endLatLng,
9047
- isSelected: isConnectingToStakeholderSelected,
9048
- id,
9049
- zoom,
9050
- listOfPolylines: polylinesRef.current
9051
9066
  });
9052
- });
9067
+ }, [linkedNodesData, selectedMarkersId, zoom, stakeholdersOfLocation, isSelected]);
9053
9068
  return /*#__PURE__*/jsxRuntime.jsx(antd.Popover, {
9054
9069
  content: renderTooltipJsx({
9055
9070
  title: data.name,
@@ -9565,7 +9580,8 @@ function useMapHelper$1({
9565
9580
  link: link,
9566
9581
  onClickLink: onClickLink,
9567
9582
  activeStakeholder: activeStakeholder,
9568
- setActiveStakeholder: setActiveStakeholder
9583
+ setActiveStakeholder: setActiveStakeholder,
9584
+ mapRef: mapRef
9569
9585
  }));
9570
9586
  roots.current.push(root);
9571
9587
  } else if (type === "location") {
@@ -9770,7 +9786,8 @@ const useMap = ({
9770
9786
  MAP_TOKEN
9771
9787
  } = useMapConfig({
9772
9788
  app,
9773
- isSatellite
9789
+ isSatellite,
9790
+ mapRef: container
9774
9791
  });
9775
9792
  const [initialMarkerSetIsDone, setInitialMarkerSetIsDone] = React.useState(false);
9776
9793
  const [mapCenter, setMapCenter] = React.useState([0, 0]);
@@ -9787,6 +9804,8 @@ const useMap = ({
9787
9804
  const graph = new Map();
9788
9805
  const stakeToLoc = new Map();
9789
9806
  const nodeTypes = new Map();
9807
+
9808
+ // Build the graph
9790
9809
  for (const loc of data) {
9791
9810
  const locId = loc.datastakeId;
9792
9811
  nodeTypes.set(locId, loc.type);
@@ -9811,26 +9830,45 @@ const useMap = ({
9811
9830
  }
9812
9831
  }
9813
9832
  const highlightTable = {};
9833
+
9834
+ // Perform BFS/DFS to find all connected nodes in the entire chain
9814
9835
  for (const [node] of graph) {
9815
9836
  const highlighted = new Set();
9816
- highlighted.add(node);
9817
- const nodeIsStakeholder = !isLocation(nodeTypes.get(node));
9818
- if (nodeIsStakeholder && stakeToLoc.has(node)) {
9819
- const parentLoc = stakeToLoc.get(node);
9820
- highlighted.add(parentLoc);
9821
- }
9822
- for (const neighbor of graph.get(node) || []) {
9823
- const neighborIsStakeholder = !isLocation(nodeTypes.get(neighbor));
9824
- if (neighborIsStakeholder && stakeToLoc.has(neighbor)) {
9825
- const neighborParent = stakeToLoc.get(neighbor);
9826
- if (isLocation(nodeTypes.get(node)) && neighborParent === node || nodeIsStakeholder && stakeToLoc.get(node) === neighborParent) {
9827
- highlighted.add(neighbor);
9828
- } else {
9837
+ const queue = [node];
9838
+ const visited = new Set([node]);
9839
+ while (queue.length > 0) {
9840
+ const current = queue.shift();
9841
+ highlighted.add(current);
9842
+
9843
+ // Add parent location if current is stakeholder
9844
+ const currentIsStakeholder = !isLocation(nodeTypes.get(current));
9845
+ if (currentIsStakeholder && stakeToLoc.has(current)) {
9846
+ const parentLoc = stakeToLoc.get(current);
9847
+ if (!visited.has(parentLoc)) {
9848
+ highlighted.add(parentLoc);
9849
+ visited.add(parentLoc);
9850
+ queue.push(parentLoc);
9851
+ }
9852
+ }
9853
+
9854
+ // Traverse all neighbors
9855
+ for (const neighbor of graph.get(current) || []) {
9856
+ if (!visited.has(neighbor)) {
9857
+ visited.add(neighbor);
9858
+ queue.push(neighbor);
9829
9859
  highlighted.add(neighbor);
9830
- highlighted.add(neighborParent);
9860
+
9861
+ // If neighbor is stakeholder, add its parent location
9862
+ const neighborIsStakeholder = !isLocation(nodeTypes.get(neighbor));
9863
+ if (neighborIsStakeholder && stakeToLoc.has(neighbor)) {
9864
+ const neighborParent = stakeToLoc.get(neighbor);
9865
+ if (!visited.has(neighborParent)) {
9866
+ highlighted.add(neighborParent);
9867
+ visited.add(neighborParent);
9868
+ queue.push(neighborParent);
9869
+ }
9870
+ }
9831
9871
  }
9832
- } else {
9833
- highlighted.add(neighbor);
9834
9872
  }
9835
9873
  }
9836
9874
  highlightTable[node] = [...highlighted];
@@ -9895,19 +9933,29 @@ const useMap = ({
9895
9933
  });
9896
9934
  }
9897
9935
  }
9936
+ if (type === "chain" && selectedMarkersId.length === 0) {
9937
+ if (polylinesRef.current.length) {
9938
+ polylinesRef.current.forEach(polyline => {
9939
+ if (mapRef.hasLayer(polyline)) {
9940
+ mapRef.removeLayer(polyline);
9941
+ }
9942
+ });
9943
+ polylinesRef.current = [];
9944
+ }
9945
+ }
9898
9946
  clearMapMarkers();
9899
9947
  if (data) {
9900
- // Filters out locations that are not connected to any stakeholders
9901
- const excludedType = ['village', 'town', 'area', 'territory'];
9902
- const filteredData = data?.filter(obj => !excludedType.includes(obj?.type) && (obj?.stakeholders?.length > 0 || data.some(other => other.datastakeId !== obj.datastakeId && (other.stakeholders || []).some(stk => (stk.links || []).includes(obj.datastakeId)))));
9948
+ const filteredData = data?.filter(obj => obj.type === 'mineSite' || obj?.stakeholders?.length > 0 || data.some(other => other.datastakeId !== obj.datastakeId && (other.stakeholders || []).some(stk => (stk.links || []).includes(obj.datastakeId))));
9903
9949
  const maxTotal = Math.max(...(data || []).map(d => d.total));
9904
9950
  const dataToRender = type === "chain" ? filteredData : data;
9905
9951
  dataToRender.forEach((d, i) => {
9906
9952
  addIconToMapInitialy([d?.marker?.lat, d?.marker?.lng], "location", d.category || "mineSite", d, maxTotal, i);
9907
9953
  });
9908
- polylinesRef.current.forEach(polyline => {
9909
- mapRef.addLayer(polyline);
9910
- });
9954
+ if (selectedMarkersId.length > 0) {
9955
+ polylinesRef.current.forEach(polyline => {
9956
+ mapRef.addLayer(polyline);
9957
+ });
9958
+ }
9911
9959
  mapRef.invalidateSize();
9912
9960
  mapRef.fire("moveend");
9913
9961
  }
@@ -40837,6 +40885,25 @@ styled__default["default"].div`
40837
40885
  }
40838
40886
  `;
40839
40887
 
40888
+ /**
40889
+ * Formats a number for display with locale-specific formatting
40890
+ * @param {any} val - The value to format
40891
+ * @param {boolean} doubleDigit - Whether to ensure single digit numbers are padded with a leading zero
40892
+ * @returns {string} Formatted number string or '--' if input is not a number
40893
+ */
40894
+ const renderNumber = (val, doubleDigit = false) => {
40895
+ if (typeof val !== 'number') {
40896
+ return '--';
40897
+ }
40898
+ const _string = Number(val).toLocaleString('en-us');
40899
+ if (doubleDigit) {
40900
+ if (_string.length === 1) {
40901
+ return '0' + _string;
40902
+ }
40903
+ }
40904
+ return _string;
40905
+ };
40906
+
40840
40907
  styled__default["default"].div`
40841
40908
  height: 333px;
40842
40909
  width: calc(100% - 48px);
@@ -42851,6 +42918,551 @@ const calculateStatChange = (data, options = {}) => {
42851
42918
  };
42852
42919
  };
42853
42920
 
42921
+ /**
42922
+ * Formats a date based on the time filter
42923
+ * @param {dayjs.Dayjs} date - The date to format
42924
+ * @param {boolean} breakLine - Whether to add a line break (for tooltips)
42925
+ * @param {string} timeFilter - The time filter ('daily', 'weekly', 'monthly')
42926
+ * @returns {string} Formatted date string
42927
+ */
42928
+ const getFormatDate = (date, breakLine = false, timeFilter = 'monthly') => {
42929
+ switch (timeFilter) {
42930
+ case "daily":
42931
+ return date.format("DD/MM");
42932
+ case "weekly":
42933
+ return `W${renderNumber(date.week())}`;
42934
+ default:
42935
+ // Monthly format: "Dec 24", "Jan 25", etc.
42936
+
42937
+ return breakLine ? `${capitalize(date.format("MMM"))}\n${date.format("YY")}` : `${capitalize(date.format("MMM"))} ${date.format("YY")}`;
42938
+ }
42939
+ };
42940
+
42941
+ /**
42942
+ * Gets the time quantity string for dayjs operations
42943
+ * @param {string} timeFilter - The time filter ('daily', 'weekly', 'monthly')
42944
+ * @returns {string} Time quantity string ('days', 'weeks', 'months')
42945
+ */
42946
+ const getTimeQuantity = (timeFilter = 'monthly') => {
42947
+ return timeFilter === "monthly" ? "months" : timeFilter === "daily" ? "days" : "weeks";
42948
+ };
42949
+
42950
+ /**
42951
+ * Gets previous cumulative score from data before start date
42952
+ * @param {Array} dates - Array of data objects with date field
42953
+ * @param {dayjs.Dayjs} startDate - The start date
42954
+ * @param {string} timeFilter - The time filter
42955
+ * @param {string} valueField - The field name to extract value from (default: 'total')
42956
+ * @returns {Object} Object with hasPreviousData, previousCumulativeScore, previousMaxScore
42957
+ */
42958
+ const getPreviousGraphData = (dates, startDate, timeFilter, valueField = 'total') => {
42959
+ let previousCumulativeScore = 0;
42960
+ let previousMaxScore = 0;
42961
+ let hasPreviousData = false;
42962
+ dates.forEach(d => {
42963
+ const date = dayjs__default["default"](d.date, "YYYY-MM-DD");
42964
+ if (!date.isValid()) return;
42965
+ let isBeforeStart = false;
42966
+ switch (timeFilter) {
42967
+ case "daily":
42968
+ isBeforeStart = date.isBefore(startDate, 'day');
42969
+ break;
42970
+ case "weekly":
42971
+ isBeforeStart = date.isBefore(startDate, 'week');
42972
+ break;
42973
+ default:
42974
+ isBeforeStart = date.isBefore(startDate, 'month');
42975
+ break;
42976
+ }
42977
+ if (isBeforeStart) {
42978
+ hasPreviousData = true;
42979
+ const value = Number(d[valueField] || d.count || d.jobs || d.value || 0) || 0;
42980
+ previousCumulativeScore += value;
42981
+ previousMaxScore = Math.max(previousMaxScore, value);
42982
+ }
42983
+ });
42984
+ return {
42985
+ hasPreviousData,
42986
+ previousCumulativeScore,
42987
+ previousMaxScore
42988
+ };
42989
+ };
42990
+
42991
+ /**
42992
+ * Processes chart data with time filtering support
42993
+ * @param {Object} params - Parameters object
42994
+ * @param {Array} params.mainData - Array of data objects with date and value fields
42995
+ * @param {string} params.timeFilter - Time filter ('daily', 'weekly', 'monthly')
42996
+ * @param {Object} params.filters - Optional filters object with timeframe
42997
+ * @param {boolean} params.isCumulative - Whether to calculate cumulative values (default: false)
42998
+ * @param {string} params.valueField - Field name to extract value from (default: 'total', also checks 'count', 'jobs', 'value')
42999
+ * @returns {Array} Processed chart data array
43000
+ */
43001
+ const processChartDateData = ({
43002
+ mainData,
43003
+ timeFilter: filter,
43004
+ filters = {},
43005
+ isCumulative = false,
43006
+ valueField = 'total'
43007
+ }) => {
43008
+ if (!mainData || !Array.isArray(mainData) || mainData.length === 0) {
43009
+ return [];
43010
+ }
43011
+ const timeQuantity = getTimeQuantity(filter);
43012
+ const dates = mainData;
43013
+ const _data = [];
43014
+ let end = filters?.timeframe?.endDate || dayjs__default["default"]();
43015
+ let start = filters?.timeframe?.startDate || dayjs__default["default"]().add(-12, timeQuantity);
43016
+
43017
+ // Normalize start and end to period boundaries
43018
+ if (filter === "daily") {
43019
+ start = start.startOf('day');
43020
+ end = end.startOf('day');
43021
+ } else if (filter === "weekly") {
43022
+ start = start.startOf('week');
43023
+ end = end.startOf('week');
43024
+ } else {
43025
+ start = start.startOf('month');
43026
+ end = end.startOf('month');
43027
+ }
43028
+ let i = 0;
43029
+ let cumulativeScore = 0;
43030
+
43031
+ // Calculate initial cumulative value if needed
43032
+ if (isCumulative) {
43033
+ const {
43034
+ hasPreviousData,
43035
+ previousCumulativeScore
43036
+ } = getPreviousGraphData(dates, start, filter, valueField);
43037
+ cumulativeScore = hasPreviousData ? previousCumulativeScore : 0;
43038
+ }
43039
+
43040
+ // Loop until we reach the end date
43041
+ let currentDate = start.clone();
43042
+ while (currentDate.isBefore(end) || currentDate.isSame(end, filter === "daily" ? "day" : filter === "weekly" ? "week" : "month")) {
43043
+ // Filter data points that fall within this period
43044
+ const score = dates.filter(d => {
43045
+ if (!d.date) return false;
43046
+ switch (filter) {
43047
+ case "daily":
43048
+ return d.date === currentDate.format("YYYY-MM-DD");
43049
+ case "weekly":
43050
+ return dayjs__default["default"](d.date, "YYYY-MM-DD").week() === currentDate.week() && dayjs__default["default"](d.date, "YYYY-MM-DD").year() === currentDate.year();
43051
+ default:
43052
+ return dayjs__default["default"](d.date, "YYYY-MM-DD").format("YYYY-MM") === currentDate.format("YYYY-MM");
43053
+ }
43054
+ }).reduce((a, b) => {
43055
+ const value = Number(b[valueField] || b.count || b.jobs || b.value || 0) || 0;
43056
+ return a + value;
43057
+ }, 0);
43058
+ if (isCumulative) {
43059
+ cumulativeScore += score;
43060
+ }
43061
+ _data.push({
43062
+ date: getFormatDate(currentDate, false, filter),
43063
+ value: isCumulative ? cumulativeScore : score,
43064
+ period: score,
43065
+ // Period value for tooltip
43066
+ jobs: score,
43067
+ // For compatibility with jobs field
43068
+ month: currentDate.format('YYYY-MM-DD'),
43069
+ // For compatibility
43070
+ key: i
43071
+ });
43072
+ currentDate = currentDate.add(1, timeQuantity);
43073
+ i++;
43074
+ }
43075
+ return _data;
43076
+ };
43077
+
43078
+ /**
43079
+ * Formats date axis labels, checking if already formatted
43080
+ * @param {string} label - The label to format
43081
+ * @param {Function} getFormatDateFn - Function to format dates
43082
+ * @returns {string} Formatted date string
43083
+ */
43084
+ const formatDateAxis = (label, getFormatDateFn) => {
43085
+ if (!label) return label;
43086
+
43087
+ // Check if label is already in the correct format (MMM YY, DD/MM, or W#)
43088
+ // If it matches our format patterns, return as-is
43089
+ if (typeof label === 'string') {
43090
+ // Check for MMM YY format (e.g., "Dec 24", "Jan 25")
43091
+ if (/^[A-Z][a-z]{2} \d{2}$/.test(label)) {
43092
+ return label;
43093
+ }
43094
+ // Check for DD/MM format (e.g., "03/11")
43095
+ if (/^\d{2}\/\d{2}$/.test(label)) {
43096
+ return label;
43097
+ }
43098
+ // Check for W# format (e.g., "W1", "W12")
43099
+ if (/^W\d+$/.test(label)) {
43100
+ return label;
43101
+ }
43102
+ }
43103
+
43104
+ // Otherwise, try to parse and format it
43105
+ let date = dayjs__default["default"](label);
43106
+
43107
+ // If first attempt fails, try parsing as ISO date string
43108
+ if (!date.isValid() && typeof label === 'string') {
43109
+ date = dayjs__default["default"](label, ['YYYY-MM-DD', 'YYYY-MM', 'MMM YY', 'MMM YYYY', 'DD/MM'], true);
43110
+ }
43111
+
43112
+ // If it's a valid date, format it using getFormatDate
43113
+ if (date.isValid() && getFormatDateFn) {
43114
+ return getFormatDateFn(date, false);
43115
+ }
43116
+
43117
+ // Return as-is if we can't parse it
43118
+ return label;
43119
+ };
43120
+
43121
+ /**
43122
+ * Custom hook for time filtering functionality
43123
+ * Provides state management and formatting functions for time-based charts
43124
+ *
43125
+ * @param {Object} options - Configuration options
43126
+ * @param {string} options.defaultFilter - Default time filter ('daily', 'weekly', 'monthly')
43127
+ * @returns {Object} Time filter state and utilities
43128
+ */
43129
+ const useTimeFilter = ({
43130
+ defaultFilter = 'monthly'
43131
+ } = {}) => {
43132
+ const [timeFilter, setTimeFilter] = React.useState(defaultFilter);
43133
+
43134
+ // Memoized format date function bound to current timeFilter
43135
+ const getFormatDateFn = React.useCallback((date, breakLine = false) => getFormatDate(date, breakLine, timeFilter), [timeFilter]);
43136
+
43137
+ // Memoized time quantity function bound to current timeFilter
43138
+ const getTimeQuantityFn = React.useCallback(() => getTimeQuantity(timeFilter), [timeFilter]);
43139
+
43140
+ // Memoized date axis formatter
43141
+ const formatDateAxisFn = React.useMemo(() => label => formatDateAxis(label, getFormatDateFn), [getFormatDateFn]);
43142
+
43143
+ // Process chart data with current time filter
43144
+ const processData = React.useCallback(({
43145
+ mainData,
43146
+ filters = {},
43147
+ isCumulative = false,
43148
+ valueField = 'total'
43149
+ }) => {
43150
+ return processChartDateData({
43151
+ mainData,
43152
+ timeFilter,
43153
+ filters,
43154
+ isCumulative,
43155
+ valueField
43156
+ });
43157
+ }, [timeFilter]);
43158
+ return {
43159
+ timeFilter,
43160
+ setTimeFilter,
43161
+ getFormatDate: getFormatDateFn,
43162
+ getTimeQuantity: getTimeQuantityFn,
43163
+ formatDateAxis: formatDateAxisFn,
43164
+ processChartDateData: processData
43165
+ };
43166
+ };
43167
+
43168
+ const selectOptions$2 = [{
43169
+ label: "Daily",
43170
+ value: "daily"
43171
+ }, {
43172
+ label: "Weekly",
43173
+ value: "weekly"
43174
+ }, {
43175
+ label: "Monthly",
43176
+ value: "monthly"
43177
+ }];
43178
+ const RestoredArea = ({
43179
+ restoredAreaChart,
43180
+ t = s => s
43181
+ }) => {
43182
+ const {
43183
+ timeFilter,
43184
+ setTimeFilter,
43185
+ formatDateAxis,
43186
+ processChartDateData
43187
+ } = useTimeFilter({
43188
+ defaultFilter: 'monthly'
43189
+ });
43190
+
43191
+ // Map restoredAreaChart data to LineChart format with time filter support
43192
+ // Y-axis: total/cumulated value
43193
+ // X-axis: date (formatted based on timeFilter)
43194
+ // Fill all periods in the range, even if empty
43195
+ const restoredAreaChartData = React.useMemo(() => {
43196
+ if (!restoredAreaChart || !Array.isArray(restoredAreaChart) || restoredAreaChart.length === 0) {
43197
+ return [];
43198
+ }
43199
+
43200
+ // Process data with cumulative calculation (for restored area)
43201
+ return processChartDateData({
43202
+ mainData: restoredAreaChart,
43203
+ isCumulative: true,
43204
+ valueField: 'total'
43205
+ });
43206
+ }, [restoredAreaChart, processChartDateData]);
43207
+
43208
+ // Calculate max value for yAxis dynamically (round up to nearest 10)
43209
+ const maxYValue = React.useMemo(() => {
43210
+ if (!restoredAreaChartData || restoredAreaChartData.length === 0) {
43211
+ return 80;
43212
+ }
43213
+ const maxValue = Math.max(...restoredAreaChartData.map(item => item.value || 0));
43214
+ // If max is 0, set default to 80 to show Y-axis
43215
+ // Otherwise, round up to nearest 10
43216
+ if (maxValue === 0) {
43217
+ return 80;
43218
+ }
43219
+ return Math.ceil(maxValue / 10) * 10 || 80;
43220
+ }, [restoredAreaChartData]);
43221
+ return /*#__PURE__*/jsxRuntime.jsx(Widget, {
43222
+ title: t("Restored Area"),
43223
+ className: "with-border-header h-w-btn-header",
43224
+ addedHeader: /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
43225
+ children: [/*#__PURE__*/jsxRuntime.jsx("div", {
43226
+ className: "flex-1"
43227
+ }), /*#__PURE__*/jsxRuntime.jsx(antd.Select, {
43228
+ value: timeFilter,
43229
+ style: {
43230
+ width: 100
43231
+ },
43232
+ onChange: value => setTimeFilter(value),
43233
+ options: selectOptions$2,
43234
+ popupMatchSelectWidth: 120
43235
+ })]
43236
+ }),
43237
+ children: /*#__PURE__*/jsxRuntime.jsx("div", {
43238
+ className: "flex flex-1 flex-column justify-content-center",
43239
+ children: /*#__PURE__*/jsxRuntime.jsx("div", {
43240
+ className: "flex justify-content-center w-full",
43241
+ children: /*#__PURE__*/jsxRuntime.jsx(LineChart, {
43242
+ animated: true,
43243
+ isArea: true,
43244
+ color: '#00AEB1',
43245
+ data: restoredAreaChartData,
43246
+ fillOpacity: 0.7,
43247
+ height: 200,
43248
+ renderTooltipContent: (title, data) => {
43249
+ if (!data || data.length === 0) return {};
43250
+ const item = data[0]?.data || data[0];
43251
+ const periodValue = item?.period !== undefined ? item.period : item?.value || 0;
43252
+ const cumulatedValue = item?.value || 0;
43253
+ return {
43254
+ title: t("Restored Area"),
43255
+ subTitle: formatDateAxis(title),
43256
+ items: [{
43257
+ label: t("Period"),
43258
+ value: `${periodValue.toLocaleString()} ha`
43259
+ }, {
43260
+ label: t("Cumulated"),
43261
+ value: `${cumulatedValue.toLocaleString()} ha`
43262
+ }]
43263
+ };
43264
+ },
43265
+ xFieldKey: "date",
43266
+ yFieldKey: "value",
43267
+ formattedXAxis: formatDateAxis,
43268
+ style: {
43269
+ width: '100%'
43270
+ },
43271
+ yAxis: {
43272
+ min: 0,
43273
+ max: maxYValue,
43274
+ tickMethod: () => {
43275
+ // Generate ticks every 10 units for maxYValue of 80
43276
+ // For other values, show ticks every 10 units
43277
+ const step = maxYValue <= 80 ? 10 : Math.max(10, Math.floor(maxYValue / 8));
43278
+ const ticks = [];
43279
+ for (let i = 0; i <= maxYValue; i += step) {
43280
+ ticks.push(i);
43281
+ }
43282
+ // Ensure max value is included
43283
+ if (ticks.length === 0 || ticks[ticks.length - 1] < maxYValue) {
43284
+ ticks.push(maxYValue);
43285
+ }
43286
+ return ticks;
43287
+ },
43288
+ label: {
43289
+ style: {
43290
+ fontSize: 12,
43291
+ fill: '#666'
43292
+ }
43293
+ },
43294
+ grid: {
43295
+ line: {
43296
+ style: {
43297
+ stroke: '#E5E7EB',
43298
+ lineWidth: 1
43299
+ }
43300
+ }
43301
+ }
43302
+ },
43303
+ xAxis: {
43304
+ label: {
43305
+ formatter: formatDateAxis,
43306
+ style: {
43307
+ fontSize: 12,
43308
+ fill: '#666'
43309
+ }
43310
+ }
43311
+ }
43312
+ })
43313
+ })
43314
+ })
43315
+ });
43316
+ };
43317
+
43318
+ const selectOptions$1 = [{
43319
+ label: "Daily",
43320
+ value: "daily"
43321
+ }, {
43322
+ label: "Weekly",
43323
+ value: "weekly"
43324
+ }, {
43325
+ label: "Monthly",
43326
+ value: "monthly"
43327
+ }];
43328
+ const PlantingActivitiesTimeline = ({
43329
+ activitiesTimelineChart,
43330
+ t = s => s
43331
+ }) => {
43332
+ const {
43333
+ timeFilter,
43334
+ setTimeFilter,
43335
+ formatDateAxis,
43336
+ processChartDateData
43337
+ } = useTimeFilter({
43338
+ defaultFilter: 'monthly'
43339
+ });
43340
+
43341
+ // Map activitiesTimelineChart data to ColumnChart format with time filter support
43342
+ // Data structure: [{count: 2, date: "2025-11-03"}]
43343
+ // Fill all periods in the range, even if empty
43344
+ const activitiesTimelineData = React.useMemo(() => {
43345
+ if (!activitiesTimelineChart || !Array.isArray(activitiesTimelineChart) || activitiesTimelineChart.length === 0) {
43346
+ return [];
43347
+ }
43348
+
43349
+ // Process data without cumulative calculation (for activities timeline)
43350
+ return processChartDateData({
43351
+ mainData: activitiesTimelineChart,
43352
+ isCumulative: false,
43353
+ valueField: 'count'
43354
+ });
43355
+ }, [activitiesTimelineChart, processChartDateData]);
43356
+
43357
+ // Calculate max value for Y-axis (default to 100 if all values are 0 or very small)
43358
+ const maxActivitiesYValue = React.useMemo(() => {
43359
+ if (!activitiesTimelineData || activitiesTimelineData.length === 0) {
43360
+ return 100;
43361
+ }
43362
+ const maxValue = Math.max(...activitiesTimelineData.map(item => item.jobs || 0));
43363
+ // If max is 0, set default to 100 to show Y-axis
43364
+ if (maxValue === 0) {
43365
+ return 100;
43366
+ }
43367
+ // Round up to nearest 10, but ensure minimum of 100
43368
+ const roundedMax = Math.ceil(maxValue / 10) * 10;
43369
+ return Math.max(100, roundedMax);
43370
+ }, [activitiesTimelineData]);
43371
+ return /*#__PURE__*/jsxRuntime.jsx(Widget, {
43372
+ title: t("Planting Activities Timeline"),
43373
+ className: "with-border-header h-w-btn-header",
43374
+ addedHeader: /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
43375
+ children: [/*#__PURE__*/jsxRuntime.jsx("div", {
43376
+ className: "flex-1"
43377
+ }), /*#__PURE__*/jsxRuntime.jsx(antd.Select, {
43378
+ value: timeFilter,
43379
+ style: {
43380
+ width: 100
43381
+ },
43382
+ onChange: value => setTimeFilter(value),
43383
+ options: selectOptions$1,
43384
+ popupMatchSelectWidth: 120
43385
+ })]
43386
+ }),
43387
+ children: /*#__PURE__*/jsxRuntime.jsx("div", {
43388
+ className: "flex flex-1 flex-column justify-content-center",
43389
+ children: /*#__PURE__*/jsxRuntime.jsx("div", {
43390
+ className: "flex justify-content-center w-full",
43391
+ children: /*#__PURE__*/jsxRuntime.jsx(ColumnChart, {
43392
+ data: activitiesTimelineData,
43393
+ xFieldKey: "date",
43394
+ yFieldKey: "jobs",
43395
+ animated: true,
43396
+ height: 400,
43397
+ color: "#016C6E",
43398
+ renderTooltipContent: (title, data) => {
43399
+ if (!data || data.length === 0) return {};
43400
+ // For ColumnChart, data structure: data[0]?.data contains the actual data point
43401
+ const item = data[0]?.data || data[0];
43402
+ const count = item?.jobs || item?.value || 0;
43403
+ // Title is the X-axis value (month/date), use it for formatting
43404
+ const dateValue = item?.date || title || '';
43405
+ return {
43406
+ title: t("Planting Activities"),
43407
+ subTitle: formatDateAxis(dateValue),
43408
+ items: [{
43409
+ label: t("Total"),
43410
+ value: count
43411
+ }]
43412
+ };
43413
+ },
43414
+ formattedXAxis: formatDateAxis,
43415
+ formattedYAxis: value => {
43416
+ return `${value}`.replace(/\d{1,3}(?=(\d{3})+$)/g, s => `${s},`);
43417
+ },
43418
+ yAxis: {
43419
+ min: 0,
43420
+ max: maxActivitiesYValue,
43421
+ tickMethod: () => {
43422
+ // Generate ticks: for 100 max, show 0, 20, 40, 60, 80, 100
43423
+ // For other values, show ticks every 20 units
43424
+ const step = maxActivitiesYValue <= 100 ? 20 : Math.max(20, Math.floor(maxActivitiesYValue / 5));
43425
+ const ticks = [];
43426
+ for (let i = 0; i <= maxActivitiesYValue; i += step) {
43427
+ ticks.push(i);
43428
+ }
43429
+ // Ensure max value is included
43430
+ if (ticks.length === 0 || ticks[ticks.length - 1] < maxActivitiesYValue) {
43431
+ ticks.push(maxActivitiesYValue);
43432
+ }
43433
+ return ticks;
43434
+ },
43435
+ label: {
43436
+ style: {
43437
+ fontSize: 12,
43438
+ fill: '#666'
43439
+ }
43440
+ },
43441
+ grid: {
43442
+ line: {
43443
+ style: {
43444
+ stroke: '#E5E7EB',
43445
+ lineWidth: 1
43446
+ }
43447
+ }
43448
+ }
43449
+ },
43450
+ xAxis: {
43451
+ label: {
43452
+ formatter: formatDateAxis,
43453
+ autoHide: true,
43454
+ style: {
43455
+ fontSize: 12,
43456
+ fill: '#666'
43457
+ }
43458
+ }
43459
+ }
43460
+ })
43461
+ })
43462
+ })
43463
+ });
43464
+ };
43465
+
42854
43466
  const CycleOutcomes = ({
42855
43467
  id,
42856
43468
  getSummaryDetail,
@@ -42911,223 +43523,6 @@ const CycleOutcomes = ({
42911
43523
  format: 'absolute'
42912
43524
  });
42913
43525
  }, [locationsCount, t]);
42914
-
42915
- // Map restoredAreaChart data to LineChart format
42916
- // Y-axis: total/cumulated value
42917
- // X-axis: date
42918
- // Fill all months in the range, even if empty
42919
- const restoredAreaChartData = React.useMemo(() => {
42920
- // Always show last 12 months, even if no data
42921
- const now = dayjs__default["default"]().startOf('month');
42922
- const twelveMonthsAgo = now.subtract(11, 'month'); // 11 months ago + current month = 12 months
42923
-
42924
- // Create a map of existing data by month (YYYY-MM format)
42925
- const dataMap = new Map();
42926
- const dates = [];
42927
-
42928
- // Process restored area data if available
42929
- if (restoredAreaChart && Array.isArray(restoredAreaChart) && restoredAreaChart.length > 0) {
42930
- restoredAreaChart.forEach(item => {
42931
- if (typeof item === 'object' && item !== null) {
42932
- // Priority: look for date field first
42933
- const dateValue = item.date || item.label || item.name || item.period || item.month;
42934
- if (dateValue) {
42935
- const date = dayjs__default["default"](dateValue);
42936
- if (date.isValid()) {
42937
- const monthKey = date.format('YYYY-MM');
42938
- // Total/cumulated value for Y-axis (this is what gets plotted)
42939
- const totalValue = Number(item.cumulated || item.cumulative || item.total || item.value || 0) || 0;
42940
- // Period value for tooltip only
42941
- const periodValue = Number(item.period || item.area || item.count || 0) || 0;
42942
- dates.push(date);
42943
-
42944
- // If multiple entries for same month, use the latest one (or sum if needed)
42945
- if (!dataMap.has(monthKey) || dataMap.get(monthKey).value < totalValue) {
42946
- dataMap.set(monthKey, {
42947
- date: dateValue,
42948
- value: totalValue,
42949
- period: periodValue
42950
- });
42951
- }
42952
- }
42953
- }
42954
- }
42955
- });
42956
- }
42957
-
42958
- // Determine date range
42959
- let minDate = twelveMonthsAgo;
42960
- let maxDate = now;
42961
-
42962
- // If we have data, adjust range to include it
42963
- if (dates.length > 0) {
42964
- const sortedDates = dates.sort((a, b) => a.valueOf() - b.valueOf());
42965
- const firstDataDate = sortedDates[0].startOf('month');
42966
- const lastDataDate = sortedDates[sortedDates.length - 1].startOf('month');
42967
-
42968
- // Start from the earlier of: 12 months ago, or first data date
42969
- minDate = twelveMonthsAgo.isBefore(firstDataDate) ? twelveMonthsAgo : firstDataDate;
42970
-
42971
- // End at the later of: current month, or last data date
42972
- maxDate = now.isAfter(lastDataDate) ? now : lastDataDate;
42973
- }
42974
-
42975
- // Generate all months in the range
42976
- const result = [];
42977
- let currentDate = minDate.clone();
42978
- let lastKnownValue = 0; // For cumulative data, carry forward the last known value
42979
-
42980
- while (currentDate.isBefore(maxDate) || currentDate.isSame(maxDate, 'month')) {
42981
- const monthKey = currentDate.format('YYYY-MM');
42982
- const existingData = dataMap.get(monthKey);
42983
- if (existingData) {
42984
- lastKnownValue = existingData.value;
42985
- result.push(existingData);
42986
- } else {
42987
- // Fill missing month - for cumulative data, use last known value
42988
- result.push({
42989
- date: currentDate.format('YYYY-MM-DD'),
42990
- value: lastKnownValue,
42991
- // Carry forward cumulative value
42992
- period: 0
42993
- });
42994
- }
42995
- currentDate = currentDate.add(1, 'month');
42996
- }
42997
- return result;
42998
- }, [restoredAreaChart]);
42999
-
43000
- // Calculate max value for yAxis dynamically (round up to nearest 10)
43001
- const maxYValue = React.useMemo(() => {
43002
- if (!restoredAreaChartData || restoredAreaChartData.length === 0) {
43003
- return 80;
43004
- }
43005
- const maxValue = Math.max(...restoredAreaChartData.map(item => item.value || 0));
43006
- // If max is 0, set default to 80 to show Y-axis
43007
- // Otherwise, round up to nearest 10
43008
- if (maxValue === 0) {
43009
- return 80;
43010
- }
43011
- return Math.ceil(maxValue / 10) * 10 || 80;
43012
- }, [restoredAreaChartData]);
43013
-
43014
- // Format date to "Mmm YY" format for X-axis
43015
- const formatDateAxis = React.useMemo(() => {
43016
- return label => {
43017
- if (!label) return label;
43018
-
43019
- // Try to parse the date using dayjs with various formats
43020
- let date = dayjs__default["default"](label);
43021
-
43022
- // If first attempt fails, try parsing as ISO date string
43023
- if (!date.isValid() && typeof label === 'string') {
43024
- date = dayjs__default["default"](label, ['YYYY-MM-DD', 'YYYY-MM', 'MMM YY', 'MMM YYYY'], true);
43025
- }
43026
-
43027
- // If it's a valid date, format it as "Mmm YY"
43028
- if (date.isValid()) {
43029
- return date.format('MMM YY');
43030
- }
43031
-
43032
- // If it's already in "Mmm YY" format or similar, return as is
43033
- // Otherwise return the original label
43034
- return label;
43035
- };
43036
- }, []);
43037
-
43038
- // Map activitiesTimelineChart data to ColumnChart format
43039
- // Data structure: [{count: 2, date: "2025-11-03"}]
43040
- // Fill all months in the range, even if empty
43041
- const activitiesTimelineData = React.useMemo(() => {
43042
- // Always show last 12 months, even if no data
43043
- const now = dayjs__default["default"]().startOf('month');
43044
- const twelveMonthsAgo = now.subtract(11, 'month'); // 11 months ago + current month = 12 months
43045
-
43046
- // Create a map of existing data by month (YYYY-MM format)
43047
- const dataMap = new Map();
43048
- const dates = [];
43049
-
43050
- // Process activities timeline data if available
43051
- if (activitiesTimelineChart && Array.isArray(activitiesTimelineChart) && activitiesTimelineChart.length > 0) {
43052
- activitiesTimelineChart.forEach(item => {
43053
- if (typeof item === 'object' && item !== null && item.date) {
43054
- const date = dayjs__default["default"](item.date);
43055
- if (date.isValid()) {
43056
- const monthKey = date.format('YYYY-MM');
43057
- const count = Number(item.count || item.jobs || item.value || 0) || 0;
43058
- dates.push(date);
43059
-
43060
- // If multiple entries for same month, sum them
43061
- if (dataMap.has(monthKey)) {
43062
- dataMap.set(monthKey, {
43063
- ...dataMap.get(monthKey),
43064
- jobs: dataMap.get(monthKey).jobs + count
43065
- });
43066
- } else {
43067
- dataMap.set(monthKey, {
43068
- month: item.date,
43069
- jobs: count,
43070
- date: item.date
43071
- });
43072
- }
43073
- }
43074
- }
43075
- });
43076
- }
43077
-
43078
- // Determine date range
43079
- let minDate = twelveMonthsAgo;
43080
- let maxDate = now;
43081
-
43082
- // If we have data, adjust range to include it
43083
- if (dates.length > 0) {
43084
- const sortedDates = dates.sort((a, b) => a.valueOf() - b.valueOf());
43085
- const firstDataDate = sortedDates[0].startOf('month');
43086
- const lastDataDate = sortedDates[sortedDates.length - 1].startOf('month');
43087
-
43088
- // Start from the earlier of: 12 months ago, or first data date
43089
- minDate = twelveMonthsAgo.isBefore(firstDataDate) ? twelveMonthsAgo : firstDataDate;
43090
-
43091
- // End at the later of: current month, or last data date
43092
- maxDate = now.isAfter(lastDataDate) ? now : lastDataDate;
43093
- }
43094
-
43095
- // Generate all months in the range
43096
- const result = [];
43097
- let currentDate = minDate.clone();
43098
- while (currentDate.isBefore(maxDate) || currentDate.isSame(maxDate, 'month')) {
43099
- const monthKey = currentDate.format('YYYY-MM');
43100
- const existingData = dataMap.get(monthKey);
43101
- if (existingData) {
43102
- result.push(existingData);
43103
- } else {
43104
- // Fill missing month with 0
43105
- result.push({
43106
- month: currentDate.format('YYYY-MM-DD'),
43107
- // Use first day of month
43108
- jobs: 0,
43109
- date: currentDate.format('YYYY-MM-DD')
43110
- });
43111
- }
43112
- currentDate = currentDate.add(1, 'month');
43113
- }
43114
- return result;
43115
- }, [activitiesTimelineChart]);
43116
-
43117
- // Calculate max value for Y-axis (default to 100 if all values are 0 or very small)
43118
- const maxActivitiesYValue = React.useMemo(() => {
43119
- if (!activitiesTimelineData || activitiesTimelineData.length === 0) {
43120
- return 100;
43121
- }
43122
- const maxValue = Math.max(...activitiesTimelineData.map(item => item.jobs || 0));
43123
- // If max is 0, set default to 100 to show Y-axis
43124
- if (maxValue === 0) {
43125
- return 100;
43126
- }
43127
- // Round up to nearest 10, but ensure minimum of 100
43128
- const roundedMax = Math.ceil(maxValue / 10) * 10;
43129
- return Math.max(100, roundedMax);
43130
- }, [activitiesTimelineData]);
43131
43526
  return /*#__PURE__*/jsxRuntime.jsx("section", {
43132
43527
  children: /*#__PURE__*/jsxRuntime.jsxs(Widget, {
43133
43528
  title: t("Restoration Cycle Outcomes"),
@@ -43169,172 +43564,17 @@ const CycleOutcomes = ({
43169
43564
  style: {
43170
43565
  flex: 1
43171
43566
  },
43172
- children: /*#__PURE__*/jsxRuntime.jsx(Widget, {
43173
- title: t("Restored Area"),
43174
- className: "with-border-header h-w-btn-header",
43175
- children: /*#__PURE__*/jsxRuntime.jsx("div", {
43176
- className: "flex flex-1 flex-column justify-content-center",
43177
- children: /*#__PURE__*/jsxRuntime.jsx("div", {
43178
- className: "flex justify-content-center w-full",
43179
- children: /*#__PURE__*/jsxRuntime.jsx(LineChart, {
43180
- animated: true,
43181
- isArea: true,
43182
- color: '#00AEB1',
43183
- data: restoredAreaChartData,
43184
- fillOpacity: 0.7,
43185
- height: 200,
43186
- renderTooltipContent: (title, data) => {
43187
- if (!data || data.length === 0) return {};
43188
- const item = data[0]?.data || data[0];
43189
- const periodValue = item?.period !== undefined ? item.period : item?.value || 0;
43190
- const cumulatedValue = item?.value || 0;
43191
- return {
43192
- title: t("Restored Area"),
43193
- subTitle: formatDateAxis(title),
43194
- items: [{
43195
- label: t("Period"),
43196
- value: `${periodValue.toLocaleString()} ha`
43197
- }, {
43198
- label: t("Cumulated"),
43199
- value: `${cumulatedValue.toLocaleString()} ha`
43200
- }]
43201
- };
43202
- },
43203
- xFieldKey: "date",
43204
- yFieldKey: "value",
43205
- formattedXAxis: formatDateAxis,
43206
- style: {
43207
- width: '100%'
43208
- },
43209
- yAxis: {
43210
- min: 0,
43211
- max: maxYValue,
43212
- tickMethod: () => {
43213
- // Generate ticks every 10 units for maxYValue of 80
43214
- // For other values, show ticks every 10 units
43215
- const step = maxYValue <= 80 ? 10 : Math.max(10, Math.floor(maxYValue / 8));
43216
- const ticks = [];
43217
- for (let i = 0; i <= maxYValue; i += step) {
43218
- ticks.push(i);
43219
- }
43220
- // Ensure max value is included
43221
- if (ticks.length === 0 || ticks[ticks.length - 1] < maxYValue) {
43222
- ticks.push(maxYValue);
43223
- }
43224
- return ticks;
43225
- },
43226
- label: {
43227
- style: {
43228
- fontSize: 12,
43229
- fill: '#666'
43230
- }
43231
- },
43232
- grid: {
43233
- line: {
43234
- style: {
43235
- stroke: '#E5E7EB',
43236
- lineWidth: 1
43237
- }
43238
- }
43239
- }
43240
- },
43241
- xAxis: {
43242
- label: {
43243
- formatter: formatDateAxis,
43244
- // Ensure formatter is applied
43245
- style: {
43246
- fontSize: 12,
43247
- fill: '#666'
43248
- }
43249
- }
43250
- }
43251
- })
43252
- })
43253
- })
43567
+ children: /*#__PURE__*/jsxRuntime.jsx(RestoredArea, {
43568
+ restoredAreaChart: restoredAreaChart,
43569
+ t: t
43254
43570
  })
43255
43571
  }), /*#__PURE__*/jsxRuntime.jsx("section", {
43256
43572
  style: {
43257
43573
  flex: 1
43258
43574
  },
43259
- children: /*#__PURE__*/jsxRuntime.jsx(Widget, {
43260
- title: t("Planting Activities Timeline"),
43261
- className: "with-border-header h-w-btn-header",
43262
- children: /*#__PURE__*/jsxRuntime.jsx("div", {
43263
- className: "flex flex-1 flex-column justify-content-center",
43264
- children: /*#__PURE__*/jsxRuntime.jsx("div", {
43265
- className: "flex justify-content-center w-full",
43266
- children: /*#__PURE__*/jsxRuntime.jsx(ColumnChart, {
43267
- data: activitiesTimelineData,
43268
- xFieldKey: "month",
43269
- yFieldKey: "jobs",
43270
- animated: true,
43271
- height: 400,
43272
- color: "#016C6E",
43273
- renderTooltipContent: (title, data) => {
43274
- if (!data || data.length === 0) return {};
43275
- // For ColumnChart, data structure: data[0]?.data contains the actual data point
43276
- const item = data[0]?.data || data[0];
43277
- const count = item?.jobs || item?.value || 0;
43278
- // Title is the X-axis value (month/date), use it for formatting
43279
- const dateValue = item?.date || title || '';
43280
- return {
43281
- title: t("Planting Activities"),
43282
- subTitle: formatDateAxis(dateValue),
43283
- items: [{
43284
- label: t("Total"),
43285
- value: count
43286
- }]
43287
- };
43288
- },
43289
- formattedXAxis: formatDateAxis,
43290
- formattedYAxis: value => {
43291
- return `${value}`.replace(/\d{1,3}(?=(\d{3})+$)/g, s => `${s},`);
43292
- },
43293
- yAxis: {
43294
- min: 0,
43295
- max: maxActivitiesYValue,
43296
- tickMethod: () => {
43297
- // Generate ticks: for 100 max, show 0, 20, 40, 60, 80, 100
43298
- // For other values, show ticks every 20 units
43299
- const step = maxActivitiesYValue <= 100 ? 20 : Math.max(20, Math.floor(maxActivitiesYValue / 5));
43300
- const ticks = [];
43301
- for (let i = 0; i <= maxActivitiesYValue; i += step) {
43302
- ticks.push(i);
43303
- }
43304
- // Ensure max value is included
43305
- if (ticks.length === 0 || ticks[ticks.length - 1] < maxActivitiesYValue) {
43306
- ticks.push(maxActivitiesYValue);
43307
- }
43308
- return ticks;
43309
- },
43310
- label: {
43311
- style: {
43312
- fontSize: 12,
43313
- fill: '#666'
43314
- }
43315
- },
43316
- grid: {
43317
- line: {
43318
- style: {
43319
- stroke: '#E5E7EB',
43320
- lineWidth: 1
43321
- }
43322
- }
43323
- }
43324
- },
43325
- xAxis: {
43326
- label: {
43327
- formatter: formatDateAxis,
43328
- autoHide: true,
43329
- style: {
43330
- fontSize: 12,
43331
- fill: '#666'
43332
- }
43333
- }
43334
- }
43335
- })
43336
- })
43337
- })
43575
+ children: /*#__PURE__*/jsxRuntime.jsx(PlantingActivitiesTimeline, {
43576
+ activitiesTimelineChart: activitiesTimelineChart,
43577
+ t: t
43338
43578
  })
43339
43579
  })]
43340
43580
  })]
@@ -43460,8 +43700,8 @@ const CyclePartners = ({
43460
43700
  };
43461
43701
 
43462
43702
  const HEALTH_SAFETY_COLORS = {
43463
- compliant: '#52C41A',
43464
- notCompliant: '#FF4D4F',
43703
+ compliant: '#016C6E',
43704
+ notCompliant: '#F97066',
43465
43705
  empty: '#D9D9D9'
43466
43706
  };
43467
43707
  const getIndicatorType = value => {
@@ -43530,8 +43770,9 @@ const isHealthAndSafetyDistributionEmpty = healthAndSafetyDistributionData => {
43530
43770
  const calculateHealthAndSafetyPieData = (healthAndSafetyDistributionData, t) => {
43531
43771
  const total = Object.values(healthAndSafetyDistributionData).reduce((all, val) => all + (val || 0), 0);
43532
43772
  const labels = {
43533
- compliant: t("Compliant"),
43534
- notCompliant: t("Not Compliant")
43773
+ compliant: t("Available"),
43774
+ notCompliant: t("Not available"),
43775
+ empty: t("Not answered")
43535
43776
  };
43536
43777
  return Object.keys(healthAndSafetyDistributionData).map(key => {
43537
43778
  const color = HEALTH_SAFETY_COLORS[key] || '#D9D9D9';
@@ -43569,8 +43810,9 @@ const getHealthAndSafetyTooltipChildren = (item, isEmpty, healthAndSafetyDistrib
43569
43810
  return null;
43570
43811
  }
43571
43812
  const labels = {
43572
- compliant: t("Compliant"),
43573
- notCompliant: t("Not Compliant")
43813
+ compliant: t("Available"),
43814
+ notCompliant: t("Not available"),
43815
+ empty: t("Not answered")
43574
43816
  };
43575
43817
 
43576
43818
  // Filter items with values > 0
@@ -43597,41 +43839,6 @@ const getHealthAndSafetyTooltipChildren = (item, isEmpty, healthAndSafetyDistrib
43597
43839
  };
43598
43840
 
43599
43841
  const HealthAndSafety = ({
43600
- activityData,
43601
- loading = false,
43602
- t = s => s
43603
- }) => {
43604
- const healthAndSafetyDistributionData = React.useMemo(() => getHealthAndSafetyDistributionData(activityData), [activityData]);
43605
- const isEmpty = React.useMemo(() => isHealthAndSafetyDistributionEmpty(healthAndSafetyDistributionData), [healthAndSafetyDistributionData]);
43606
- const pieData = React.useMemo(() => calculateHealthAndSafetyPieData(healthAndSafetyDistributionData, t), [healthAndSafetyDistributionData, t]);
43607
- const getTooltipChildren = React.useCallback(item => getHealthAndSafetyTooltipChildren(item, isEmpty, healthAndSafetyDistributionData, t, renderTooltipJsx), [t, isEmpty, healthAndSafetyDistributionData]);
43608
- return /*#__PURE__*/jsxRuntime.jsx(Widget, {
43609
- loading: loading,
43610
- title: /*#__PURE__*/jsxRuntime.jsx("div", {
43611
- children: t("Health and Safety")
43612
- }),
43613
- className: "with-border-header h-w-btn-header ",
43614
- children: /*#__PURE__*/jsxRuntime.jsx("div", {
43615
- style: {
43616
- marginTop: "auto",
43617
- marginBottom: "auto"
43618
- },
43619
- children: /*#__PURE__*/jsxRuntime.jsx(Chart, {
43620
- mouseXOffset: 10,
43621
- mouseYOffset: 10,
43622
- changeOpacityOnHover: false,
43623
- data: pieData,
43624
- doConstraints: false,
43625
- isPie: true,
43626
- t: t,
43627
- isEmpty: isEmpty,
43628
- getTooltipChildren: getTooltipChildren
43629
- })
43630
- })
43631
- });
43632
- };
43633
-
43634
- const CycleIndicators = ({
43635
43842
  id,
43636
43843
  getSummaryDetail,
43637
43844
  loading = false,
@@ -43639,18 +43846,26 @@ const CycleIndicators = ({
43639
43846
  }) => {
43640
43847
  const defaultConfig = React.useMemo(() => ({
43641
43848
  basepath: "planting-cycle",
43642
- url: `/summary/${id}/indicators`,
43849
+ url: `/summary/${id}/filtered-piechart`,
43850
+ filters: {
43851
+ field: 'duosFormed'
43852
+ },
43643
43853
  stop: !id
43644
43854
  }), [id]);
43645
43855
  const customGetData = React.useMemo(() => {
43646
43856
  if (getSummaryDetail && id) {
43647
- return ({
43648
- url,
43649
- params = {}
43650
- }) => {
43857
+ return rest => {
43858
+ const {
43859
+ url,
43860
+ filters: restFilters
43861
+ } = rest;
43651
43862
  const match = url.match(/\/summary\/[^/]+\/(.+)/);
43652
43863
  if (match) {
43653
43864
  const [, type] = match;
43865
+ // Pass filters as params for the query string
43866
+ const params = {
43867
+ ...(restFilters || {})
43868
+ };
43654
43869
  return getSummaryDetail(id, type, params);
43655
43870
  }
43656
43871
  throw new Error(`Invalid URL format: ${url}`);
@@ -43659,213 +43874,60 @@ const CycleIndicators = ({
43659
43874
  return undefined;
43660
43875
  }, [getSummaryDetail, id]);
43661
43876
  const {
43662
- loading: indicatorsLoading,
43663
- data: indicatorsData
43877
+ loading: pieChartLoading,
43878
+ data: pieChartData
43664
43879
  } = useWidgetFetch({
43665
43880
  config: defaultConfig,
43666
43881
  getData: customGetData
43667
43882
  });
43668
- const {
43669
- participantsCount,
43670
- eventIncidents,
43671
- cyclePartners
43672
- } = indicatorsData;
43673
- const participantsCountChange = React.useMemo(() => {
43674
- if (!participantsCount) return null;
43675
- return calculateStatChange({
43676
- current: Number(participantsCount.current) || 0,
43677
- previous: Number(participantsCount.previous) || 0
43678
- }, {
43679
- tooltipText: t("In comparison to last cycle"),
43680
- format: 'absolute'
43681
- });
43682
- }, [participantsCount, t]);
43683
- const eventIncidentsChange = React.useMemo(() => {
43684
- if (!eventIncidents) return null;
43685
- return calculateStatChange({
43686
- current: Number(eventIncidents.current) || 0,
43687
- previous: Number(eventIncidents.previous) || 0
43688
- }, {
43689
- tooltipText: t("In comparison to last cycle"),
43690
- format: 'absolute'
43691
- });
43692
- }, [eventIncidents, t]);
43693
- return /*#__PURE__*/jsxRuntime.jsx("section", {
43694
- children: /*#__PURE__*/jsxRuntime.jsxs(Widget, {
43695
- title: t("Cycle Indicators"),
43696
- loading: loading,
43697
- className: "with-border-header h-w-btn-header",
43698
- children: [/*#__PURE__*/jsxRuntime.jsxs("div", {
43699
- style: {
43700
- display: 'flex',
43701
- gap: '24px',
43702
- marginBottom: '24px'
43703
- },
43704
- children: [/*#__PURE__*/jsxRuntime.jsx("section", {
43705
- style: {
43706
- flex: 1
43707
- },
43708
- children: /*#__PURE__*/jsxRuntime.jsx(StatCard, {
43709
- title: t("Activity Participants"),
43710
- value: participantsCount?.current ? Number(participantsCount?.current).toLocaleString() : 0,
43711
- icon: "partnerIcon",
43712
- change: participantsCountChange
43713
- })
43714
- }), /*#__PURE__*/jsxRuntime.jsx("section", {
43715
- style: {
43716
- flex: 1
43717
- },
43718
- children: /*#__PURE__*/jsxRuntime.jsx(StatCard, {
43719
- title: t("Reported Incidents"),
43720
- value: eventIncidents?.current || 0,
43721
- icon: "ReportIncidents",
43722
- change: eventIncidentsChange
43723
- })
43724
- })]
43725
- }), /*#__PURE__*/jsxRuntime.jsxs("div", {
43726
- style: {
43727
- display: 'flex',
43728
- gap: '24px'
43729
- },
43730
- children: [/*#__PURE__*/jsxRuntime.jsx("section", {
43731
- style: {
43732
- flex: 1
43733
- },
43734
- children: /*#__PURE__*/jsxRuntime.jsx(CyclePartners, {
43735
- cyclePartners: cyclePartners,
43736
- loading: indicatorsLoading,
43737
- t: t
43738
- })
43739
- }), /*#__PURE__*/jsxRuntime.jsx("section", {
43740
- style: {
43741
- flex: 1
43742
- },
43743
- children: /*#__PURE__*/jsxRuntime.jsx(HealthAndSafety, {
43744
- activityData: indicatorsData,
43745
- loading: indicatorsLoading,
43746
- t: t
43747
- })
43748
- })]
43749
- })]
43750
- })
43751
- });
43752
- };
43753
43883
 
43754
- const GENDER_COLORS = ['#a0ebec', '#00aeb1'];
43755
- const getGenderDistributionData = genderDistribution => {
43756
- if (Array.isArray(genderDistribution) && genderDistribution.length > 0) {
43757
- const firstItem = genderDistribution[0];
43758
- if (firstItem && (firstItem.totalFemale !== undefined || firstItem.totalMale !== undefined)) {
43759
- const totalFemale = genderDistribution.reduce((sum, item) => sum + (Number(item.totalFemale) || 0), 0);
43760
- const totalMale = genderDistribution.reduce((sum, item) => sum + (Number(item.totalMale) || 0), 0);
43761
- return {
43762
- Male: totalMale,
43763
- Female: totalFemale
43764
- };
43765
- }
43766
- const distribution = {};
43767
- genderDistribution.forEach(item => {
43768
- const gender = item?.gender || item?.label || item?.name || 'Unknown';
43769
- const count = item?.count || item?.value || item?.total || 0;
43770
- const normalizedGender = gender.toLowerCase() === 'male' ? 'Male' : gender.toLowerCase() === 'female' ? 'Female' : gender;
43771
- distribution[normalizedGender] = (distribution[normalizedGender] || 0) + count;
43772
- });
43773
- return distribution;
43774
- }
43775
- if (genderDistribution && typeof genderDistribution === 'object' && !Array.isArray(genderDistribution)) {
43776
- if (genderDistribution.genderDistribution && typeof genderDistribution.genderDistribution === 'object') {
43777
- return genderDistribution.genderDistribution;
43778
- }
43779
- if (genderDistribution.totalFemale !== undefined || genderDistribution.totalMale !== undefined) {
43780
- return {
43781
- Male: Number(genderDistribution.totalMale) || 0,
43782
- Female: Number(genderDistribution.totalFemale) || 0
43783
- };
43784
- }
43785
- if (genderDistribution.Male !== undefined || genderDistribution.Female !== undefined || genderDistribution.male !== undefined || genderDistribution.female !== undefined) {
43786
- return {
43787
- Male: genderDistribution.Male || genderDistribution.male || 0,
43788
- Female: genderDistribution.Female || genderDistribution.female || 0
43789
- };
43790
- }
43791
- return genderDistribution;
43792
- }
43793
- return {};
43794
- };
43795
-
43796
- /**
43797
- * Checks if the gender distribution data is empty
43798
- * @param {Object} genderDistributionData - Distribution object: { "Male": 10, "Female": 5 }
43799
- * @returns {boolean} True if all values are 0 or empty
43800
- */
43801
- const isGenderDistributionEmpty = genderDistributionData => {
43802
- return Object.values(genderDistributionData).every(val => !val || val === 0);
43803
- };
43804
-
43805
- /**
43806
- * Calculates pie chart data from gender distribution
43807
- * @param {Object} genderDistributionData - Distribution object: { "Male": 10, "Female": 5 }
43808
- * @returns {Array} Array of pie chart data points with value, percent, color, label, and key
43809
- */
43810
- const calculateGenderPieData = genderDistributionData => {
43811
- const total = Object.values(genderDistributionData).reduce((all, val) => all + (val || 0), 0);
43812
- return Object.keys(genderDistributionData).map((key, index) => {
43813
- const color = GENDER_COLORS[index % GENDER_COLORS.length];
43814
- return {
43815
- value: genderDistributionData[key] || 0,
43816
- percent: total > 0 ? (genderDistributionData[key] || 0) / total : 0,
43817
- color: color,
43818
- label: key,
43819
- key: key
43884
+ // Process the fetched pie chart data
43885
+ // The API returns data in format: [{count: 1, duosFormed: "null"}, {count: 1, duosFormed: "no"}, {count: 1, duosFormed: "yes"}]
43886
+ const healthAndSafetyDistributionData = React.useMemo(() => {
43887
+ if (!pieChartData) return {
43888
+ compliant: 0,
43889
+ notCompliant: 0,
43890
+ empty: 0
43820
43891
  };
43821
- });
43822
- };
43823
- const getGenderTooltipChildren = (item, isEmpty, genderDistributionData, t, renderTooltipJsx) => {
43824
- if (isEmpty) {
43825
- if (!Object.keys(genderDistributionData).length) {
43826
- return null;
43827
- }
43828
- return renderTooltipJsx({
43829
- title: t("Gender Distribution"),
43830
- items: Object.keys(genderDistributionData).map(k => ({
43831
- color: GENDER_COLORS[Object.keys(genderDistributionData).indexOf(k) % GENDER_COLORS.length],
43832
- label: k,
43833
- value: '0%'
43834
- }))
43835
- });
43836
- }
43837
43892
 
43838
- // Calculate total for percentage calculation
43839
- const total = Object.values(genderDistributionData).reduce((all, val) => all + (val || 0), 0);
43893
+ // If it's already a distribution object
43894
+ if (pieChartData.compliant !== undefined || pieChartData.notCompliant !== undefined) {
43895
+ return pieChartData;
43896
+ }
43840
43897
 
43841
- // Show all gender items with percentages (matching the image format)
43842
- return renderTooltipJsx({
43843
- title: t("Gender Distribution"),
43844
- items: Object.keys(genderDistributionData).map((k, index) => {
43845
- const value = genderDistributionData[k] || 0;
43846
- const percent = total > 0 ? Math.round(value / total * 100) : 0;
43847
- return {
43848
- color: GENDER_COLORS[index % GENDER_COLORS.length],
43849
- label: k,
43850
- value: `${percent}%`
43898
+ // If it's an array, process it
43899
+ if (Array.isArray(pieChartData)) {
43900
+ const distribution = {
43901
+ compliant: 0,
43902
+ notCompliant: 0,
43903
+ empty: 0
43851
43904
  };
43852
- })
43853
- });
43854
- };
43905
+ pieChartData.forEach(item => {
43906
+ const duosFormedValue = item.duosFormed;
43907
+ const count = item.count || 0;
43908
+
43909
+ // Map duosFormed values to distribution categories
43910
+ if (duosFormedValue === "yes" || duosFormedValue === true) {
43911
+ distribution.compliant += count;
43912
+ } else if (duosFormedValue === "no" || duosFormedValue === false) {
43913
+ distribution.notCompliant += count;
43914
+ } else if (duosFormedValue === "null" || duosFormedValue === null || duosFormedValue === undefined) {
43915
+ distribution.empty += count;
43916
+ }
43917
+ });
43918
+ return distribution;
43919
+ }
43855
43920
 
43856
- const GenderDistribution = ({
43857
- genderDistribution,
43858
- loading = false,
43859
- t = s => s
43860
- }) => {
43861
- const genderDistributionData = React.useMemo(() => getGenderDistributionData(genderDistribution), [genderDistribution]);
43862
- const isEmpty = React.useMemo(() => isGenderDistributionEmpty(genderDistributionData), [genderDistributionData]);
43863
- const pieData = React.useMemo(() => calculateGenderPieData(genderDistributionData), [genderDistributionData]);
43864
- const getTooltipChildren = React.useCallback(item => getGenderTooltipChildren(item, isEmpty, genderDistributionData, t, renderTooltipJsx), [t, isEmpty, genderDistributionData]);
43921
+ // Fallback: try to extract from activityData-like structure
43922
+ return getHealthAndSafetyDistributionData(pieChartData);
43923
+ }, [pieChartData]);
43924
+ const isEmpty = React.useMemo(() => isHealthAndSafetyDistributionEmpty(healthAndSafetyDistributionData), [healthAndSafetyDistributionData]);
43925
+ const pieData = React.useMemo(() => calculateHealthAndSafetyPieData(healthAndSafetyDistributionData, t), [healthAndSafetyDistributionData, t]);
43926
+ const getTooltipChildren = React.useCallback(item => getHealthAndSafetyTooltipChildren(item, isEmpty, healthAndSafetyDistributionData, t, renderTooltipJsx), [t, isEmpty, healthAndSafetyDistributionData]);
43865
43927
  return /*#__PURE__*/jsxRuntime.jsx(Widget, {
43866
- loading: loading,
43928
+ loading: loading || pieChartLoading,
43867
43929
  title: /*#__PURE__*/jsxRuntime.jsx("div", {
43868
- children: t("Gender Distribution")
43930
+ children: t("Health and Safety")
43869
43931
  }),
43870
43932
  className: "with-border-header h-w-btn-header ",
43871
43933
  children: /*#__PURE__*/jsxRuntime.jsx("div", {
@@ -43888,109 +43950,301 @@ const GenderDistribution = ({
43888
43950
  });
43889
43951
  };
43890
43952
 
43891
- const JobsTimeline = ({
43892
- dayJobsTimeline,
43953
+ const CycleIndicators = ({
43954
+ id,
43955
+ getSummaryDetail,
43893
43956
  loading = false,
43894
43957
  t = s => s
43895
43958
  }) => {
43896
- const jobsData = Array.isArray(dayJobsTimeline) ? dayJobsTimeline : dayJobsTimeline?.jobsTimeline || dayJobsTimeline?.jobs || dayJobsTimeline?.timeline || [];
43897
- const formatDateAxis = React.useMemo(() => {
43898
- return label => {
43899
- if (!label) return label;
43900
-
43901
- // Try to parse the date using dayjs with various formats
43902
- let date = dayjs__default["default"](label);
43959
+ const defaultConfig = React.useMemo(() => ({
43960
+ basepath: "planting-cycle",
43961
+ url: `/summary/${id}/indicators`,
43962
+ stop: !id
43963
+ }), [id]);
43964
+ const customGetData = React.useMemo(() => {
43965
+ if (getSummaryDetail && id) {
43966
+ return ({
43967
+ url,
43968
+ params = {}
43969
+ }) => {
43970
+ const match = url.match(/\/summary\/[^/]+\/(.+)/);
43971
+ if (match) {
43972
+ const [, type] = match;
43973
+ return getSummaryDetail(id, type, params);
43974
+ }
43975
+ throw new Error(`Invalid URL format: ${url}`);
43976
+ };
43977
+ }
43978
+ return undefined;
43979
+ }, [getSummaryDetail, id]);
43980
+ const {
43981
+ loading: indicatorsLoading,
43982
+ data: indicatorsData
43983
+ } = useWidgetFetch({
43984
+ config: defaultConfig,
43985
+ getData: customGetData
43986
+ });
43987
+ const {
43988
+ participantsCount,
43989
+ eventIncidents,
43990
+ cyclePartners
43991
+ } = indicatorsData;
43992
+ const participantsCountChange = React.useMemo(() => {
43993
+ if (!participantsCount) return null;
43994
+ return calculateStatChange({
43995
+ current: Number(participantsCount.current) || 0,
43996
+ previous: Number(participantsCount.previous) || 0
43997
+ }, {
43998
+ tooltipText: t("In comparison to last cycle"),
43999
+ format: 'absolute'
44000
+ });
44001
+ }, [participantsCount, t]);
44002
+ const eventIncidentsChange = React.useMemo(() => {
44003
+ if (!eventIncidents) return null;
44004
+ return calculateStatChange({
44005
+ current: Number(eventIncidents.current) || 0,
44006
+ previous: Number(eventIncidents.previous) || 0
44007
+ }, {
44008
+ tooltipText: t("In comparison to last cycle"),
44009
+ format: 'absolute'
44010
+ });
44011
+ }, [eventIncidents, t]);
44012
+ return /*#__PURE__*/jsxRuntime.jsx("section", {
44013
+ children: /*#__PURE__*/jsxRuntime.jsxs(Widget, {
44014
+ title: t("Cycle Indicators"),
44015
+ loading: loading,
44016
+ className: "with-border-header h-w-btn-header",
44017
+ children: [/*#__PURE__*/jsxRuntime.jsxs("div", {
44018
+ style: {
44019
+ display: 'flex',
44020
+ gap: '24px',
44021
+ marginBottom: '24px'
44022
+ },
44023
+ children: [/*#__PURE__*/jsxRuntime.jsx("section", {
44024
+ style: {
44025
+ flex: 1
44026
+ },
44027
+ children: /*#__PURE__*/jsxRuntime.jsx(StatCard, {
44028
+ title: t("Activity Participants"),
44029
+ value: participantsCount?.current ? Number(participantsCount?.current).toLocaleString() : 0,
44030
+ icon: "partnerIcon",
44031
+ change: participantsCountChange
44032
+ })
44033
+ }), /*#__PURE__*/jsxRuntime.jsx("section", {
44034
+ style: {
44035
+ flex: 1
44036
+ },
44037
+ children: /*#__PURE__*/jsxRuntime.jsx(StatCard, {
44038
+ title: t("Reported Incidents"),
44039
+ value: eventIncidents?.current || 0,
44040
+ icon: "ReportIncidents",
44041
+ change: eventIncidentsChange
44042
+ })
44043
+ })]
44044
+ }), /*#__PURE__*/jsxRuntime.jsxs("div", {
44045
+ style: {
44046
+ display: 'flex',
44047
+ gap: '24px'
44048
+ },
44049
+ children: [/*#__PURE__*/jsxRuntime.jsx("section", {
44050
+ style: {
44051
+ flex: 1
44052
+ },
44053
+ children: /*#__PURE__*/jsxRuntime.jsx(CyclePartners, {
44054
+ cyclePartners: cyclePartners,
44055
+ loading: indicatorsLoading,
44056
+ t: t
44057
+ })
44058
+ }), /*#__PURE__*/jsxRuntime.jsx("section", {
44059
+ style: {
44060
+ flex: 1
44061
+ },
44062
+ children: /*#__PURE__*/jsxRuntime.jsx(HealthAndSafety, {
44063
+ id: id,
44064
+ getSummaryDetail: getSummaryDetail,
44065
+ loading: indicatorsLoading,
44066
+ t: t
44067
+ })
44068
+ })]
44069
+ })]
44070
+ })
44071
+ });
44072
+ };
43903
44073
 
43904
- // If first attempt fails, try parsing as ISO date string
43905
- if (!date.isValid() && typeof label === 'string') {
43906
- date = dayjs__default["default"](label, ['YYYY-MM-DD', 'YYYY-MM', 'MMM YY', 'MMM YYYY'], true);
43907
- }
44074
+ const GENDER_COLORS = ['#a0ebec', '#00aeb1'];
44075
+ const getGenderDistributionData = genderDistribution => {
44076
+ if (Array.isArray(genderDistribution) && genderDistribution.length > 0) {
44077
+ const firstItem = genderDistribution[0];
44078
+ if (firstItem && (firstItem.totalFemale !== undefined || firstItem.totalMale !== undefined)) {
44079
+ const totalFemale = genderDistribution.reduce((sum, item) => sum + (Number(item.totalFemale) || 0), 0);
44080
+ const totalMale = genderDistribution.reduce((sum, item) => sum + (Number(item.totalMale) || 0), 0);
44081
+ return {
44082
+ Male: totalMale,
44083
+ Female: totalFemale
44084
+ };
44085
+ }
44086
+ const distribution = {};
44087
+ genderDistribution.forEach(item => {
44088
+ const gender = item?.gender || item?.label || item?.name || 'Unknown';
44089
+ const count = item?.count || item?.value || item?.total || 0;
44090
+ const normalizedGender = gender.toLowerCase() === 'male' ? 'Male' : gender.toLowerCase() === 'female' ? 'Female' : gender;
44091
+ distribution[normalizedGender] = (distribution[normalizedGender] || 0) + count;
44092
+ });
44093
+ return distribution;
44094
+ }
44095
+ if (genderDistribution && typeof genderDistribution === 'object' && !Array.isArray(genderDistribution)) {
44096
+ if (genderDistribution.genderDistribution && typeof genderDistribution.genderDistribution === 'object') {
44097
+ return genderDistribution.genderDistribution;
44098
+ }
44099
+ if (genderDistribution.totalFemale !== undefined || genderDistribution.totalMale !== undefined) {
44100
+ return {
44101
+ Male: Number(genderDistribution.totalMale) || 0,
44102
+ Female: Number(genderDistribution.totalFemale) || 0
44103
+ };
44104
+ }
44105
+ if (genderDistribution.Male !== undefined || genderDistribution.Female !== undefined || genderDistribution.male !== undefined || genderDistribution.female !== undefined) {
44106
+ return {
44107
+ Male: genderDistribution.Male || genderDistribution.male || 0,
44108
+ Female: genderDistribution.Female || genderDistribution.female || 0
44109
+ };
44110
+ }
44111
+ return genderDistribution;
44112
+ }
44113
+ return {};
44114
+ };
43908
44115
 
43909
- // If it's a valid date, format it as "Mmm YY"
43910
- if (date.isValid()) {
43911
- return date.format('MMM YY');
43912
- }
44116
+ /**
44117
+ * Checks if the gender distribution data is empty
44118
+ * @param {Object} genderDistributionData - Distribution object: { "Male": 10, "Female": 5 }
44119
+ * @returns {boolean} True if all values are 0 or empty
44120
+ */
44121
+ const isGenderDistributionEmpty = genderDistributionData => {
44122
+ return Object.values(genderDistributionData).every(val => !val || val === 0);
44123
+ };
43913
44124
 
43914
- // If it's already in "Mmm YY" format or similar, return as is
43915
- // Otherwise return the original label
43916
- return label;
44125
+ /**
44126
+ * Calculates pie chart data from gender distribution
44127
+ * @param {Object} genderDistributionData - Distribution object: { "Male": 10, "Female": 5 }
44128
+ * @returns {Array} Array of pie chart data points with value, percent, color, label, and key
44129
+ */
44130
+ const calculateGenderPieData = genderDistributionData => {
44131
+ const total = Object.values(genderDistributionData).reduce((all, val) => all + (val || 0), 0);
44132
+ return Object.keys(genderDistributionData).map((key, index) => {
44133
+ const color = GENDER_COLORS[index % GENDER_COLORS.length];
44134
+ return {
44135
+ value: genderDistributionData[key] || 0,
44136
+ percent: total > 0 ? (genderDistributionData[key] || 0) / total : 0,
44137
+ color: color,
44138
+ label: key,
44139
+ key: key
43917
44140
  };
43918
- }, []);
43919
- const jobsTimelineData = React.useMemo(() => {
43920
- // Always show last 12 months, even if no data
43921
- const now = dayjs__default["default"]().startOf('month');
43922
- const twelveMonthsAgo = now.subtract(11, 'month'); // 11 months ago + current month = 12 months
43923
-
43924
- // Create a map of existing data by month (YYYY-MM format)
43925
- const dataMap = new Map();
43926
- const dates = [];
43927
-
43928
- // Process jobs data if available
43929
- if (jobsData && Array.isArray(jobsData) && jobsData.length > 0) {
43930
- jobsData.forEach(item => {
43931
- if (typeof item === 'object' && item !== null && item.date) {
43932
- const date = dayjs__default["default"](item.date);
43933
- if (date.isValid()) {
43934
- const monthKey = date.format('YYYY-MM');
43935
- const count = Number(item.total || item.count || item.jobs || item.value || 0) || 0;
43936
- dates.push(date);
43937
-
43938
- // If multiple entries for same month, sum them
43939
- if (dataMap.has(monthKey)) {
43940
- dataMap.set(monthKey, {
43941
- ...dataMap.get(monthKey),
43942
- jobs: dataMap.get(monthKey).jobs + count
43943
- });
43944
- } else {
43945
- dataMap.set(monthKey, {
43946
- month: item.date,
43947
- jobs: count,
43948
- date: item.date
43949
- });
43950
- }
43951
- }
43952
- }
43953
- });
44141
+ });
44142
+ };
44143
+ const getGenderTooltipChildren = (item, isEmpty, genderDistributionData, t, renderTooltipJsx) => {
44144
+ if (isEmpty) {
44145
+ if (!Object.keys(genderDistributionData).length) {
44146
+ return null;
43954
44147
  }
44148
+ return renderTooltipJsx({
44149
+ title: t("Gender Distribution"),
44150
+ items: Object.keys(genderDistributionData).map(k => ({
44151
+ color: GENDER_COLORS[Object.keys(genderDistributionData).indexOf(k) % GENDER_COLORS.length],
44152
+ label: k,
44153
+ value: '0%'
44154
+ }))
44155
+ });
44156
+ }
43955
44157
 
43956
- // Determine date range
43957
- let minDate = twelveMonthsAgo;
43958
- let maxDate = now;
44158
+ // Calculate total for percentage calculation
44159
+ const total = Object.values(genderDistributionData).reduce((all, val) => all + (val || 0), 0);
43959
44160
 
43960
- // If we have data, adjust range to include it
43961
- if (dates.length > 0) {
43962
- const sortedDates = dates.sort((a, b) => a.valueOf() - b.valueOf());
43963
- const firstDataDate = sortedDates[0].startOf('month');
43964
- const lastDataDate = sortedDates[sortedDates.length - 1].startOf('month');
44161
+ // Show all gender items with percentages (matching the image format)
44162
+ return renderTooltipJsx({
44163
+ title: t("Gender Distribution"),
44164
+ items: Object.keys(genderDistributionData).map((k, index) => {
44165
+ const value = genderDistributionData[k] || 0;
44166
+ const percent = total > 0 ? Math.round(value / total * 100) : 0;
44167
+ return {
44168
+ color: GENDER_COLORS[index % GENDER_COLORS.length],
44169
+ label: k,
44170
+ value: `${percent}%`
44171
+ };
44172
+ })
44173
+ });
44174
+ };
43965
44175
 
43966
- // Start from the earlier of: 12 months ago, or first data date
43967
- minDate = twelveMonthsAgo.isBefore(firstDataDate) ? twelveMonthsAgo : firstDataDate;
44176
+ const GenderDistribution = ({
44177
+ genderDistribution,
44178
+ loading = false,
44179
+ t = s => s
44180
+ }) => {
44181
+ const genderDistributionData = React.useMemo(() => getGenderDistributionData(genderDistribution), [genderDistribution]);
44182
+ const isEmpty = React.useMemo(() => isGenderDistributionEmpty(genderDistributionData), [genderDistributionData]);
44183
+ const pieData = React.useMemo(() => calculateGenderPieData(genderDistributionData), [genderDistributionData]);
44184
+ const getTooltipChildren = React.useCallback(item => getGenderTooltipChildren(item, isEmpty, genderDistributionData, t, renderTooltipJsx), [t, isEmpty, genderDistributionData]);
44185
+ return /*#__PURE__*/jsxRuntime.jsx(Widget, {
44186
+ loading: loading,
44187
+ title: /*#__PURE__*/jsxRuntime.jsx("div", {
44188
+ children: t("Gender Distribution")
44189
+ }),
44190
+ className: "with-border-header h-w-btn-header ",
44191
+ children: /*#__PURE__*/jsxRuntime.jsx("div", {
44192
+ style: {
44193
+ marginTop: "auto",
44194
+ marginBottom: "auto"
44195
+ },
44196
+ children: /*#__PURE__*/jsxRuntime.jsx(Chart, {
44197
+ mouseXOffset: 10,
44198
+ mouseYOffset: 10,
44199
+ changeOpacityOnHover: false,
44200
+ data: pieData,
44201
+ doConstraints: false,
44202
+ isPie: true,
44203
+ t: t,
44204
+ isEmpty: isEmpty,
44205
+ getTooltipChildren: getTooltipChildren
44206
+ })
44207
+ })
44208
+ });
44209
+ };
43968
44210
 
43969
- // End at the later of: current month, or last data date
43970
- maxDate = now.isAfter(lastDataDate) ? now : lastDataDate;
44211
+ const selectOptions = [{
44212
+ label: "Daily",
44213
+ value: "daily"
44214
+ }, {
44215
+ label: "Weekly",
44216
+ value: "weekly"
44217
+ }, {
44218
+ label: "Monthly",
44219
+ value: "monthly"
44220
+ }];
44221
+ const JobsTimeline = ({
44222
+ dayJobsTimeline,
44223
+ loading = false,
44224
+ t = s => s
44225
+ }) => {
44226
+ const {
44227
+ timeFilter,
44228
+ setTimeFilter,
44229
+ formatDateAxis,
44230
+ processChartDateData
44231
+ } = useTimeFilter({
44232
+ defaultFilter: 'monthly'
44233
+ });
44234
+ const jobsData = Array.isArray(dayJobsTimeline) ? dayJobsTimeline : dayJobsTimeline?.jobsTimeline || dayJobsTimeline?.jobs || dayJobsTimeline?.timeline || [];
44235
+ const jobsTimelineData = React.useMemo(() => {
44236
+ if (!jobsData || !Array.isArray(jobsData) || jobsData.length === 0) {
44237
+ return [];
43971
44238
  }
43972
44239
 
43973
- // Generate all months in the range
43974
- const result = [];
43975
- let currentDate = minDate.clone();
43976
- while (currentDate.isBefore(maxDate) || currentDate.isSame(maxDate, 'month')) {
43977
- const monthKey = currentDate.format('YYYY-MM');
43978
- const existingData = dataMap.get(monthKey);
43979
- if (existingData) {
43980
- result.push(existingData);
43981
- } else {
43982
- // Fill missing month with 0
43983
- result.push({
43984
- month: currentDate.format('YYYY-MM-DD'),
43985
- // Use first day of month
43986
- jobs: 0,
43987
- date: currentDate.format('YYYY-MM-DD')
43988
- });
43989
- }
43990
- currentDate = currentDate.add(1, 'month');
43991
- }
43992
- return result;
43993
- }, [jobsData]);
44240
+ // Process data without cumulative calculation (for jobs timeline)
44241
+ // Try to find value in total, count, jobs, or value fields
44242
+ return processChartDateData({
44243
+ mainData: jobsData,
44244
+ isCumulative: false,
44245
+ valueField: 'total' // Will fallback to count/jobs/value if total doesn't exist
44246
+ });
44247
+ }, [jobsData, processChartDateData]);
43994
44248
  const maxYValue = React.useMemo(() => {
43995
44249
  if (!jobsTimelineData || jobsTimelineData.length === 0) {
43996
44250
  return 100;
@@ -44008,9 +44262,22 @@ const JobsTimeline = ({
44008
44262
  children: t("Day Jobs Timeline")
44009
44263
  }),
44010
44264
  className: "with-border-header h-w-btn-header ",
44265
+ addedHeader: /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
44266
+ children: [/*#__PURE__*/jsxRuntime.jsx("div", {
44267
+ className: "flex-1"
44268
+ }), /*#__PURE__*/jsxRuntime.jsx(antd.Select, {
44269
+ value: timeFilter,
44270
+ style: {
44271
+ width: 100
44272
+ },
44273
+ onChange: value => setTimeFilter(value),
44274
+ options: selectOptions,
44275
+ popupMatchSelectWidth: 120
44276
+ })]
44277
+ }),
44011
44278
  children: /*#__PURE__*/jsxRuntime.jsx(ColumnChart, {
44012
44279
  data: jobsTimelineData,
44013
- xFieldKey: "month",
44280
+ xFieldKey: "date",
44014
44281
  yFieldKey: "jobs",
44015
44282
  animated: true
44016
44283
  // height={200}
@@ -44377,7 +44644,7 @@ const getColumns = ({
44377
44644
  }
44378
44645
  return /*#__PURE__*/jsxRuntime.jsx(antd.Tooltip, {
44379
44646
  title: value,
44380
- children: value?.toUpperCase().charAt(0) + value?.slice(1) || '-'
44647
+ children: capitalize(value) || '-'
44381
44648
  });
44382
44649
  }
44383
44650
  }, {
@@ -44628,19 +44895,10 @@ const getColumns = ({
44628
44895
  className: "daf-default-cell"
44629
44896
  });
44630
44897
  }
44631
- let country = '-';
44632
- if (value) {
44633
- country = value;
44634
- } else if (all.country?.name) {
44635
- country = all.country.name;
44636
- } else if (all.location && typeof all.location === 'object' && all.location.country) {
44637
- country = all.location.country;
44638
- } else if (all.location && typeof all.location === 'object' && all.location.name) {
44639
- country = all.location.name;
44640
- }
44898
+ const title = findOptions(value, selectOptions?.country || []) || '-';
44641
44899
  return /*#__PURE__*/jsxRuntime.jsx(antd.Tooltip, {
44642
- title: country,
44643
- children: country
44900
+ title: title,
44901
+ children: title
44644
44902
  });
44645
44903
  }
44646
44904
  }, {
@@ -44892,6 +45150,7 @@ const AssociatedInformation = ({
44892
45150
  endpoint = "associated-information",
44893
45151
  tabsConfig = TABS_CONFIG,
44894
45152
  searchFieldsMap = getSearchFields,
45153
+ selectOptions,
44895
45154
  t = s => s
44896
45155
  }) => {
44897
45156
  const [activeTab, setActiveTab] = React.useState(ACTIVITIES_TAB);
@@ -44948,8 +45207,9 @@ const AssociatedInformation = ({
44948
45207
  view: activeTab,
44949
45208
  projectId,
44950
45209
  navigate,
44951
- getRedirectLink
44952
- }), [t, activeTab, projectId, navigate]);
45210
+ getRedirectLink,
45211
+ selectOptions
45212
+ }), [t, activeTab, projectId, navigate, selectOptions]);
44953
45213
  const tableDataSource = React.useMemo(() => ensureArray(associatedInformationData), [associatedInformationData]);
44954
45214
  const translatedTabs = React.useMemo(() => tabsConfig.map(tab => ({
44955
45215
  ...tab,
@@ -45084,7 +45344,8 @@ const PlantingCycleSummary = ({
45084
45344
  projectId,
45085
45345
  t = () => {},
45086
45346
  getSummaryDetail,
45087
- navigate
45347
+ navigate,
45348
+ selectOptions
45088
45349
  }) => {
45089
45350
  return /*#__PURE__*/jsxRuntime.jsxs(DashboardLayout, {
45090
45351
  header: /*#__PURE__*/jsxRuntime.jsx(DAFHeader, {
@@ -45128,7 +45389,8 @@ const PlantingCycleSummary = ({
45128
45389
  getSummaryDetail: getSummaryDetail,
45129
45390
  loading: loading,
45130
45391
  t: t,
45131
- navigate: navigate
45392
+ navigate: navigate,
45393
+ selectOptions: selectOptions
45132
45394
  })]
45133
45395
  });
45134
45396
  };