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.
- package/dist/components/index.js +2656 -2476
- package/dist/hooks/index.js +72 -0
- package/dist/pages/index.js +1211 -949
- package/dist/utils/index.js +13 -0
- package/package.json +1 -1
- package/src/@daf/core/components/Dashboard/Map/ChainIcon/Markers/StakeholderMarker.js +8 -76
- package/src/@daf/core/components/Dashboard/Map/ChainIcon/index.js +116 -8
- package/src/@daf/core/components/Dashboard/Map/ChainIcon/utils.js +73 -17
- package/src/@daf/core/components/Dashboard/Map/helper.js +1 -0
- package/src/@daf/core/components/Dashboard/Map/hook.js +53 -29
- package/src/@daf/core/components/Dashboard/Map/style.js +20 -5
- package/src/@daf/hooks/useTimeFilter.js +56 -0
- package/src/@daf/hooks/useViewFormUrlParams.js +84 -0
- package/src/@daf/pages/Summary/Activities/PlantingCycle/components/AssociatedInformation/config.js +7 -13
- package/src/@daf/pages/Summary/Activities/PlantingCycle/components/AssociatedInformation/index.jsx +3 -1
- package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CommunityParticipation/JobsTimeline/index.jsx +33 -101
- package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CycleIndicators/HealthAndSafety/helper.js +8 -6
- package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CycleIndicators/HealthAndSafety/index.jsx +73 -4
- package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CycleIndicators/index.jsx +1 -1
- package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CycleOutcomes/PlantingActivitiesTimeline.jsx +148 -0
- package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CycleOutcomes/RestoredArea.jsx +150 -0
- package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CycleOutcomes/index.jsx +11 -390
- package/src/@daf/pages/Summary/Activities/PlantingCycle/index.jsx +2 -2
- package/src/@daf/utils/object.js +3 -1
- package/src/@daf/utils/timeFilterUtils.js +226 -0
- package/src/hooks.js +2 -1
- package/src/utils.js +1 -1
package/dist/pages/index.js
CHANGED
|
@@ -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
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
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
|
|
8307
|
-
const
|
|
8308
|
-
const
|
|
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:
|
|
8311
|
-
opacity: 0.5,
|
|
8312
|
-
smoothFactor:
|
|
8345
|
+
weight: lineWidth,
|
|
8346
|
+
opacity: isSelected ? 1 : 0.5,
|
|
8347
|
+
smoothFactor: 0,
|
|
8313
8348
|
id,
|
|
8314
|
-
dashArray: !isSelected ? "5, 5" : "
|
|
8349
|
+
dashArray: isShortLink ? "0, 0" : shouldAnimate ? "10, 10" : !isSelected ? "5, 5" : "10, 10",
|
|
8350
|
+
renderer: L.svg()
|
|
8315
8351
|
};
|
|
8316
|
-
const
|
|
8317
|
-
if (
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
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
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
})
|
|
8331
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
9023
|
-
|
|
9024
|
-
|
|
9025
|
-
|
|
9026
|
-
|
|
9027
|
-
|
|
9028
|
-
|
|
9029
|
-
|
|
9030
|
-
|
|
9031
|
-
|
|
9032
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9817
|
-
const
|
|
9818
|
-
|
|
9819
|
-
const
|
|
9820
|
-
highlighted.add(
|
|
9821
|
-
|
|
9822
|
-
|
|
9823
|
-
const
|
|
9824
|
-
if (
|
|
9825
|
-
const
|
|
9826
|
-
if (
|
|
9827
|
-
highlighted.add(
|
|
9828
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9909
|
-
|
|
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(
|
|
43173
|
-
|
|
43174
|
-
|
|
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(
|
|
43260
|
-
|
|
43261
|
-
|
|
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: '#
|
|
43464
|
-
notCompliant: '#
|
|
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("
|
|
43534
|
-
notCompliant: t("Not
|
|
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("
|
|
43573
|
-
notCompliant: t("Not
|
|
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}/
|
|
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
|
-
|
|
43649
|
-
|
|
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:
|
|
43663
|
-
data:
|
|
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
|
-
|
|
43755
|
-
|
|
43756
|
-
|
|
43757
|
-
|
|
43758
|
-
|
|
43759
|
-
|
|
43760
|
-
|
|
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
|
-
|
|
43839
|
-
|
|
43893
|
+
// If it's already a distribution object
|
|
43894
|
+
if (pieChartData.compliant !== undefined || pieChartData.notCompliant !== undefined) {
|
|
43895
|
+
return pieChartData;
|
|
43896
|
+
}
|
|
43840
43897
|
|
|
43841
|
-
|
|
43842
|
-
|
|
43843
|
-
|
|
43844
|
-
|
|
43845
|
-
|
|
43846
|
-
|
|
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
|
-
|
|
43857
|
-
|
|
43858
|
-
|
|
43859
|
-
|
|
43860
|
-
|
|
43861
|
-
const
|
|
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("
|
|
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
|
|
43892
|
-
|
|
43953
|
+
const CycleIndicators = ({
|
|
43954
|
+
id,
|
|
43955
|
+
getSummaryDetail,
|
|
43893
43956
|
loading = false,
|
|
43894
43957
|
t = s => s
|
|
43895
43958
|
}) => {
|
|
43896
|
-
const
|
|
43897
|
-
|
|
43898
|
-
|
|
43899
|
-
|
|
43900
|
-
|
|
43901
|
-
|
|
43902
|
-
|
|
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
|
-
|
|
43905
|
-
|
|
43906
|
-
|
|
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
|
-
|
|
43910
|
-
|
|
43911
|
-
|
|
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
|
-
|
|
43915
|
-
|
|
43916
|
-
|
|
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
|
-
|
|
43920
|
-
|
|
43921
|
-
|
|
43922
|
-
|
|
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
|
-
|
|
43957
|
-
|
|
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
|
-
|
|
43961
|
-
|
|
43962
|
-
|
|
43963
|
-
|
|
43964
|
-
const
|
|
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
|
-
|
|
43967
|
-
|
|
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
|
-
|
|
43970
|
-
|
|
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
|
-
//
|
|
43974
|
-
|
|
43975
|
-
|
|
43976
|
-
|
|
43977
|
-
|
|
43978
|
-
|
|
43979
|
-
|
|
43980
|
-
|
|
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: "
|
|
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:
|
|
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
|
-
|
|
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:
|
|
44643
|
-
children:
|
|
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
|
-
|
|
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
|
};
|