datastake-daf 0.6.810 → 0.6.812
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 +1338 -1330
- package/dist/layouts/index.js +484 -460
- package/dist/pages/index.js +1562 -551
- package/dist/style/datastake/mapbox-gl.css +330 -0
- package/dist/utils/index.js +484 -460
- package/package.json +1 -1
- package/public/Vegetation/damage-from-insects-default.svg +2 -0
- package/public/Vegetation/dry-or-dead-default.svg +2 -0
- package/public/Vegetation/healthy-default.svg +2 -0
- package/public/Vegetation/yellowing.svg +2 -0
- package/src/@daf/core/components/Dashboard/Widget/FaunaWidget/index.jsx +1 -2
- package/src/@daf/core/components/Icon/configs/Droplets.js +9 -0
- package/src/@daf/core/components/Icon/configs/TrendUp.js +8 -0
- package/src/@daf/core/components/Icon/configs/index.js +4 -0
- package/src/@daf/pages/Summary/Activities/Monitoring/components/BiodiversityAndHabitat/index.jsx +2 -2
- package/src/@daf/pages/Summary/Activities/Monitoring/helper.js +24 -0
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/BiodiversityHabitat/InvasiveSpecies.jsx +107 -0
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/BiodiversityHabitat/ObservedFauna.jsx +80 -0
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/BiodiversityHabitat/Stats.jsx +62 -0
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/BiodiversityHabitat/index.jsx +109 -0
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/KeyInformation/index.jsx +8 -4
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MangroveGrowth/PlantedSpecies.jsx +21 -9
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MangroveGrowth/SeedlingsHeight.jsx +36 -2
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MangroveGrowth/Stats.jsx +3 -3
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MangroveGrowth/VegetationHealth.jsx +19 -8
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MangroveGrowth/index.jsx +13 -9
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/SoilWaterProfile/SalinityLevels.jsx +100 -0
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/SoilWaterProfile/SoilType.jsx +84 -0
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/SoilWaterProfile/Stats.jsx +72 -0
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/SoilWaterProfile/WaterQuality.jsx +84 -0
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/SoilWaterProfile/index.jsx +101 -0
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/chartHelpers.js +102 -0
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/index.jsx +8 -2
- package/src/@daf/utils/numbers.js +1 -1
|
@@ -6,24 +6,35 @@ const COLORS = ['#016C6E', '#F5C2AC', '#F0A888', '#DF571E', '#C04B19', '#9B3D14'
|
|
|
6
6
|
|
|
7
7
|
const VegetationHealth = ({
|
|
8
8
|
vegetationHealthChart,
|
|
9
|
-
t = (s) => s
|
|
9
|
+
t = (s) => s,
|
|
10
|
+
options = {}
|
|
10
11
|
}) => {
|
|
12
|
+
const optionsMap = useMemo(() => {
|
|
13
|
+
if (!options || !Array.isArray(options)) return {};
|
|
14
|
+
return options.reduce((acc, option) => {
|
|
15
|
+
if (option?.value) {
|
|
16
|
+
acc[option.value] = option.label;
|
|
17
|
+
}
|
|
18
|
+
return acc;
|
|
19
|
+
}, {});
|
|
20
|
+
}, [options]);
|
|
21
|
+
|
|
11
22
|
const pieData = useMemo(() => {
|
|
12
23
|
const data = vegetationHealthChart || [];
|
|
13
|
-
const total = data.reduce((sum, item) => sum + (Number(item?.
|
|
24
|
+
const total = data.reduce((sum, item) => sum + (Number(item?.count) || 0), 0);
|
|
14
25
|
|
|
15
26
|
return data.map((item, index) => ({
|
|
16
|
-
value: Number(item?.
|
|
17
|
-
percent: total > 0 ? (Number(item?.
|
|
27
|
+
value: Number(item?.count) || 0,
|
|
28
|
+
percent: total > 0 ? (Number(item?.count) || 0) / total : 0,
|
|
18
29
|
color: COLORS[index % COLORS.length],
|
|
19
|
-
label: item?.
|
|
20
|
-
key: item?.
|
|
30
|
+
label: optionsMap[item?.name] || item?.name || '',
|
|
31
|
+
key: item?.name || `item-${index}`,
|
|
21
32
|
}));
|
|
22
|
-
}, [vegetationHealthChart]);
|
|
33
|
+
}, [vegetationHealthChart, optionsMap]);
|
|
23
34
|
|
|
24
35
|
const isEmpty = useMemo(() => {
|
|
25
36
|
return !vegetationHealthChart || vegetationHealthChart.length === 0 ||
|
|
26
|
-
vegetationHealthChart.every(item => !item?.
|
|
37
|
+
vegetationHealthChart.every(item => !item?.count || Number(item.count) === 0);
|
|
27
38
|
}, [vegetationHealthChart]);
|
|
28
39
|
|
|
29
40
|
const getTooltipChildren = useCallback(
|
package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MangroveGrowth/index.jsx
CHANGED
|
@@ -10,12 +10,13 @@ const MangroveGrowth = ({
|
|
|
10
10
|
id,
|
|
11
11
|
getSummaryDetail,
|
|
12
12
|
loading = false,
|
|
13
|
-
t = (s) => s
|
|
13
|
+
t = (s) => s,
|
|
14
|
+
options = {}
|
|
14
15
|
}) => {
|
|
15
16
|
const defaultConfig = useMemo(
|
|
16
17
|
() => ({
|
|
17
|
-
basepath: "events/monitoring-campaign",
|
|
18
|
-
url: `/summary/${id}/
|
|
18
|
+
basepath: "events/monitoring-campaign-summary",
|
|
19
|
+
url: `/summary/${id}/mangrove-growth`,
|
|
19
20
|
stop: !id,
|
|
20
21
|
}),
|
|
21
22
|
[id],
|
|
@@ -44,9 +45,9 @@ const MangroveGrowth = ({
|
|
|
44
45
|
survivalRate,
|
|
45
46
|
averageHeight,
|
|
46
47
|
averageDiameter,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
vegetationHealth,
|
|
49
|
+
seedlingsTimelineChart,
|
|
50
|
+
plantedSpecies
|
|
50
51
|
} = outcomesData || {};
|
|
51
52
|
|
|
52
53
|
return (
|
|
@@ -66,20 +67,23 @@ const MangroveGrowth = ({
|
|
|
66
67
|
<div style={{ display: "flex", gap: "24px" }}>
|
|
67
68
|
<section style={{ flex: 1 }}>
|
|
68
69
|
<VegetationHealth
|
|
69
|
-
vegetationHealthChart={
|
|
70
|
+
vegetationHealthChart={vegetationHealth}
|
|
70
71
|
t={t}
|
|
72
|
+
options={options.growthObservations}
|
|
71
73
|
/>
|
|
72
74
|
</section>
|
|
73
75
|
<section style={{ flex: 1 }}>
|
|
74
76
|
<SeedlingsHeight
|
|
75
|
-
seedlingsHeightChart={
|
|
77
|
+
seedlingsHeightChart={seedlingsTimelineChart}
|
|
76
78
|
t={t}
|
|
79
|
+
options={options.seedlingsHeight}
|
|
77
80
|
/>
|
|
78
81
|
</section>
|
|
79
82
|
<section style={{ flex: 1 }}>
|
|
80
83
|
<PlantedSpecies
|
|
81
|
-
plantedSpeciesChart={
|
|
84
|
+
plantedSpeciesChart={plantedSpecies}
|
|
82
85
|
t={t}
|
|
86
|
+
options={options.mangroveSpecies}
|
|
83
87
|
/>
|
|
84
88
|
</section>
|
|
85
89
|
</div>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { Widget, ColumnChart } from '../../../../../../../../src/index.js';
|
|
3
|
+
import { renderNumber } from '../../../../../../utils/numbers.js';
|
|
4
|
+
import { calculateNiceAxisConfig } from '../chartHelpers.js';
|
|
5
|
+
|
|
6
|
+
const SOIL_TYPE_COLORS = {
|
|
7
|
+
'sandy': '#F6E1BF',
|
|
8
|
+
'clay': '#B16100',
|
|
9
|
+
'muddy': '#776D59',
|
|
10
|
+
'mixed': '#00AEB1'
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const SalinityLevels = ({
|
|
14
|
+
salinityLevelsChart,
|
|
15
|
+
t = (s) => s,
|
|
16
|
+
options = {}
|
|
17
|
+
}) => {
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
const chartData = useMemo(() => {
|
|
21
|
+
const soilTypes = options?.soilTypes || [];
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
// Create a map of existing data
|
|
25
|
+
const dataMap = new Map();
|
|
26
|
+
if (salinityLevelsChart && Array.isArray(salinityLevelsChart)) {
|
|
27
|
+
salinityLevelsChart.forEach(item => {
|
|
28
|
+
if (item?.soilType) {
|
|
29
|
+
dataMap.set(item.soilType, Number(item?.averageSalinity) || 0);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Create chart data with all soil types from options
|
|
35
|
+
const data = soilTypes.map((soilType) => ({
|
|
36
|
+
label: soilType?.label || soilType?.value || '',
|
|
37
|
+
value: dataMap.get(soilType?.value) || 0,
|
|
38
|
+
soilType: soilType?.value || '',
|
|
39
|
+
color: SOIL_TYPE_COLORS[soilType?.value] || '#B16100'
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
return data;
|
|
44
|
+
}, [salinityLevelsChart, options]);
|
|
45
|
+
|
|
46
|
+
const yAxisConfig = useMemo(() => {
|
|
47
|
+
return calculateNiceAxisConfig(
|
|
48
|
+
chartData,
|
|
49
|
+
'value',
|
|
50
|
+
1.2, // multiplier: 20% padding
|
|
51
|
+
{
|
|
52
|
+
min: 0,
|
|
53
|
+
max: 10,
|
|
54
|
+
tickMethod: () => [0, 2, 4, 6, 8, 10]
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
}, [chartData]);
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Widget
|
|
61
|
+
title={t("Salinity Levels")}
|
|
62
|
+
className="with-border-header h-w-btn-header"
|
|
63
|
+
>
|
|
64
|
+
<div className="flex flex-1 flex-column justify-content-center">
|
|
65
|
+
<div className="flex justify-content-center w-full">
|
|
66
|
+
<ColumnChart
|
|
67
|
+
data={chartData}
|
|
68
|
+
xFieldKey="label"
|
|
69
|
+
yFieldKey="value"
|
|
70
|
+
color={(item) => {
|
|
71
|
+
|
|
72
|
+
const dataItem = chartData.find(d => d.label === item.label);
|
|
73
|
+
const color = dataItem ? SOIL_TYPE_COLORS[dataItem.soilType] : '#B16100';
|
|
74
|
+
return color;
|
|
75
|
+
}}
|
|
76
|
+
animated={true}
|
|
77
|
+
height={200}
|
|
78
|
+
yAxis={yAxisConfig}
|
|
79
|
+
renderTooltipContent={(title, data) => {
|
|
80
|
+
if (!data || data.length === 0) return {};
|
|
81
|
+
const item = data[0]?.data || data[0];
|
|
82
|
+
return {
|
|
83
|
+
title: options?.soilTypes?.find(option => option.value === item?.soilType)?.label || title,
|
|
84
|
+
items: [
|
|
85
|
+
{
|
|
86
|
+
label: t("Average salinity"),
|
|
87
|
+
value: `${renderNumber(item?.value || 0)} dS/m`,
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
}}
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</Widget>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export default SalinityLevels;
|
|
100
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React, { useMemo, useCallback } from 'react';
|
|
2
|
+
import { Widget, PieChart } from '../../../../../../../../src/index.js';
|
|
3
|
+
import { renderTooltipJsx } from '../../../../../../utils/tooltip.js';
|
|
4
|
+
import { renderPercentage } from '../../../../../../utils/numbers.js';
|
|
5
|
+
|
|
6
|
+
const COLORS = ['#016C6E', '#4FB3A1', '#A8E6CF', '#FFD93D', '#F0A888', '#DF571E', '#C04B19'];
|
|
7
|
+
|
|
8
|
+
const SoilType = ({
|
|
9
|
+
soilTypeChart,
|
|
10
|
+
t = (s) => s,
|
|
11
|
+
options = {}
|
|
12
|
+
}) => {
|
|
13
|
+
|
|
14
|
+
const optionsMap = useMemo(() => {
|
|
15
|
+
if (!options?.soilTypes || !Array.isArray(options.soilTypes)) return {};
|
|
16
|
+
return options.soilTypes.reduce((acc, option) => {
|
|
17
|
+
if (option?.value) {
|
|
18
|
+
acc[option.value] = option.label;
|
|
19
|
+
}
|
|
20
|
+
return acc;
|
|
21
|
+
}, {});
|
|
22
|
+
}, [options]);
|
|
23
|
+
|
|
24
|
+
const pieData = useMemo(() => {
|
|
25
|
+
const data = soilTypeChart || [];
|
|
26
|
+
const total = data.reduce((sum, item) => sum + (Number(item?.count) || 0), 0);
|
|
27
|
+
|
|
28
|
+
return data.map((item, index) => ({
|
|
29
|
+
value: Number(item?.count) || 0,
|
|
30
|
+
percent: total > 0 ? (Number(item?.count) || 0) / total : 0,
|
|
31
|
+
color: COLORS[index % COLORS.length],
|
|
32
|
+
label: optionsMap[item?.soilType] || item?.soilType || '',
|
|
33
|
+
key: item?.soilType || `item-${index}`,
|
|
34
|
+
}));
|
|
35
|
+
}, [soilTypeChart, optionsMap]);
|
|
36
|
+
|
|
37
|
+
const isEmpty = useMemo(() => {
|
|
38
|
+
return !soilTypeChart || soilTypeChart.length === 0 ||
|
|
39
|
+
soilTypeChart.every(item => !item?.count || Number(item.count) === 0);
|
|
40
|
+
}, [soilTypeChart]);
|
|
41
|
+
|
|
42
|
+
const getTooltipChildren = useCallback(
|
|
43
|
+
(item) => {
|
|
44
|
+
if (isEmpty) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return renderTooltipJsx({
|
|
49
|
+
title: t("Soil Type"),
|
|
50
|
+
items: [
|
|
51
|
+
{
|
|
52
|
+
color: item.color,
|
|
53
|
+
label: optionsMap[item.label] || item.label || '',
|
|
54
|
+
value: `${ renderPercentage(item.percent.toFixed(2) * 100)}`,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
}, [t, isEmpty, optionsMap]);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Widget
|
|
62
|
+
title={t("Soil Type")}
|
|
63
|
+
className="with-border-header h-w-btn-header"
|
|
64
|
+
>
|
|
65
|
+
<div className="flex flex-1 flex-column justify-content-center">
|
|
66
|
+
<div className="flex justify-content-center w-full">
|
|
67
|
+
<PieChart
|
|
68
|
+
data={pieData}
|
|
69
|
+
isPie
|
|
70
|
+
isEmpty={isEmpty}
|
|
71
|
+
getTooltipChildren={getTooltipChildren}
|
|
72
|
+
mouseXOffset={10}
|
|
73
|
+
mouseYOffset={10}
|
|
74
|
+
changeOpacityOnHover={false}
|
|
75
|
+
doConstraints={false}
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</Widget>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export default SoilType;
|
|
84
|
+
|
package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/SoilWaterProfile/Stats.jsx
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { StatCard } from '../../../../../../../../src/index.js';
|
|
3
|
+
import { calculateStatChange } from '../../../../../../utils/numbers.js';
|
|
4
|
+
|
|
5
|
+
const Stats = ({
|
|
6
|
+
monitoredArea,
|
|
7
|
+
predominantSoilType,
|
|
8
|
+
averagePhLevel,
|
|
9
|
+
t = (s) => s,
|
|
10
|
+
options = {}
|
|
11
|
+
}) => {
|
|
12
|
+
const monitoredAreaChange = useMemo(() => {
|
|
13
|
+
if (!monitoredArea) return null;
|
|
14
|
+
return calculateStatChange(
|
|
15
|
+
{
|
|
16
|
+
current: Number(monitoredArea.current) || 0,
|
|
17
|
+
previous: Number(monitoredArea.previous) || 0,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
tooltipText: t("In comparison to last period"),
|
|
21
|
+
format: 'absolute',
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
}, [monitoredArea, t]);
|
|
25
|
+
|
|
26
|
+
const averagePhLevelChange = useMemo(() => {
|
|
27
|
+
if (!averagePhLevel) return null;
|
|
28
|
+
return calculateStatChange(
|
|
29
|
+
{
|
|
30
|
+
current: Number(averagePhLevel.current) || 0,
|
|
31
|
+
previous: Number(averagePhLevel.previous) || 0,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
tooltipText: t("In comparison to last period"),
|
|
35
|
+
format: 'absolute',
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
}, [averagePhLevel, t]);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div style={{ display: "flex", gap: "24px", marginBottom: "24px" }}>
|
|
42
|
+
<section style={{ flex: 1 }}>
|
|
43
|
+
<StatCard
|
|
44
|
+
title={t("Monitored Area")}
|
|
45
|
+
value={monitoredArea ? Number(monitoredArea.current).toLocaleString() + " ha" : "0 ha"}
|
|
46
|
+
icon="Tree"
|
|
47
|
+
change={monitoredAreaChange}
|
|
48
|
+
/>
|
|
49
|
+
</section>
|
|
50
|
+
|
|
51
|
+
<section style={{ flex: 1 }}>
|
|
52
|
+
<StatCard
|
|
53
|
+
title={t("Predominant Soil type")}
|
|
54
|
+
value={ options?.soilTypes?.find(option => option.value === predominantSoilType)?.label || "-"}
|
|
55
|
+
icon="Earth"
|
|
56
|
+
/>
|
|
57
|
+
</section>
|
|
58
|
+
|
|
59
|
+
<section style={{ flex: 1 }}>
|
|
60
|
+
<StatCard
|
|
61
|
+
title={t("Average pH Level")}
|
|
62
|
+
value={averagePhLevel ? Number(averagePhLevel.current).toFixed(1) : "0.0"}
|
|
63
|
+
icon="Droplets"
|
|
64
|
+
change={averagePhLevelChange}
|
|
65
|
+
/>
|
|
66
|
+
</section>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export default Stats;
|
|
72
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React, { useMemo, useCallback } from 'react';
|
|
2
|
+
import { Widget, PieChart } from '../../../../../../../../src/index.js';
|
|
3
|
+
import { renderTooltipJsx } from '../../../../../../utils/tooltip.js';
|
|
4
|
+
|
|
5
|
+
const COLORS = ['#6AD99E', '#FFD93D', '#F0A888', '#DF571E', '#B0B0B0', '#016C6E', '#4FB3A1'];
|
|
6
|
+
|
|
7
|
+
const WaterQuality = ({
|
|
8
|
+
waterQualityChart,
|
|
9
|
+
t = (s) => s,
|
|
10
|
+
options = {}
|
|
11
|
+
}) => {
|
|
12
|
+
const optionsMap = useMemo(() => {
|
|
13
|
+
if (!options?.waterQuality || !Array.isArray(options.waterQuality)) return {};
|
|
14
|
+
return options.waterQuality.reduce((acc, option) => {
|
|
15
|
+
if (option?.value) {
|
|
16
|
+
acc[option.value] = option.label;
|
|
17
|
+
}
|
|
18
|
+
return acc;
|
|
19
|
+
}, {});
|
|
20
|
+
}, [options]);
|
|
21
|
+
|
|
22
|
+
const pieData = useMemo(() => {
|
|
23
|
+
const data = waterQualityChart || [];
|
|
24
|
+
const total = data.reduce((sum, item) => sum + (Number(item?.count) || 0), 0);
|
|
25
|
+
|
|
26
|
+
return data.map((item, index) => ({
|
|
27
|
+
value: Number(item?.count) || 0,
|
|
28
|
+
percent: total > 0 ? (Number(item?.count) || 0) / total : 0,
|
|
29
|
+
color: COLORS[index % COLORS.length],
|
|
30
|
+
label: optionsMap[item?.waterQuality] || item?.waterQuality || '',
|
|
31
|
+
key: item?.waterQuality || `item-${index}`,
|
|
32
|
+
}));
|
|
33
|
+
}, [waterQualityChart, optionsMap]);
|
|
34
|
+
|
|
35
|
+
const isEmpty = useMemo(() => {
|
|
36
|
+
return !waterQualityChart || waterQualityChart.length === 0 ||
|
|
37
|
+
waterQualityChart.every(item => !item?.count || Number(item.count) === 0);
|
|
38
|
+
}, [waterQualityChart]);
|
|
39
|
+
|
|
40
|
+
const getTooltipChildren = useCallback(
|
|
41
|
+
(item) => {
|
|
42
|
+
if (isEmpty) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return renderTooltipJsx({
|
|
47
|
+
title: t("Water Quality"),
|
|
48
|
+
items: [
|
|
49
|
+
{
|
|
50
|
+
color: item.color,
|
|
51
|
+
label: item.label || '',
|
|
52
|
+
value: `${Math.round(item.percent * 100)}%`,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
[t, isEmpty]
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Widget
|
|
62
|
+
title={t("Water Quality")}
|
|
63
|
+
className="with-border-header h-w-btn-header"
|
|
64
|
+
>
|
|
65
|
+
<div className="flex flex-1 flex-column justify-content-center">
|
|
66
|
+
<div className="flex justify-content-center w-full">
|
|
67
|
+
<PieChart
|
|
68
|
+
data={pieData}
|
|
69
|
+
isPie
|
|
70
|
+
isEmpty={isEmpty}
|
|
71
|
+
getTooltipChildren={getTooltipChildren}
|
|
72
|
+
mouseXOffset={10}
|
|
73
|
+
mouseYOffset={10}
|
|
74
|
+
changeOpacityOnHover={false}
|
|
75
|
+
doConstraints={false}
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</Widget>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export default WaterQuality;
|
|
84
|
+
|
package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/SoilWaterProfile/index.jsx
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { Widget } from '../../../../../../../../src/index.js';
|
|
3
|
+
import { useWidgetFetch } from '../../../../../../hooks/useWidgetFetch.js';
|
|
4
|
+
import Stats from './Stats.jsx';
|
|
5
|
+
import SoilType from './SoilType.jsx';
|
|
6
|
+
import SalinityLevels from './SalinityLevels.jsx';
|
|
7
|
+
import WaterQuality from './WaterQuality.jsx';
|
|
8
|
+
|
|
9
|
+
const SoilWaterProfile = ({
|
|
10
|
+
id,
|
|
11
|
+
getSummaryDetail,
|
|
12
|
+
loading = false,
|
|
13
|
+
t = (s) => s,
|
|
14
|
+
options = {}
|
|
15
|
+
}) => {
|
|
16
|
+
const defaultConfig = useMemo(
|
|
17
|
+
() => ({
|
|
18
|
+
basepath: "events/monitoring-campaign-summary",
|
|
19
|
+
url: `/summary/${id}/soil-water-profile`,
|
|
20
|
+
stop: !id,
|
|
21
|
+
}),
|
|
22
|
+
[id],
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const customGetData = useMemo(() => {
|
|
26
|
+
if (getSummaryDetail && id) {
|
|
27
|
+
return ({ url, params = {} }) => {
|
|
28
|
+
const match = url.match(/\/summary\/[^/]+\/(.+)/);
|
|
29
|
+
if (match) {
|
|
30
|
+
const [, type] = match;
|
|
31
|
+
return getSummaryDetail(id, type, params);
|
|
32
|
+
}
|
|
33
|
+
throw new Error(`Invalid URL format: ${url}`);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}, [getSummaryDetail, id]);
|
|
38
|
+
|
|
39
|
+
const { loading: outcomesLoading, data: outcomesData } = useWidgetFetch({
|
|
40
|
+
config: defaultConfig,
|
|
41
|
+
getData: customGetData
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const {
|
|
45
|
+
monitoredArea,
|
|
46
|
+
predominantSoilType,
|
|
47
|
+
phLevel,
|
|
48
|
+
previousPhLevel,
|
|
49
|
+
soilType,
|
|
50
|
+
salinityLevels,
|
|
51
|
+
waterQuality
|
|
52
|
+
} = outcomesData || {};
|
|
53
|
+
|
|
54
|
+
const averagePhLevel = useMemo(() => {
|
|
55
|
+
return {
|
|
56
|
+
current: Number(phLevel) || 0,
|
|
57
|
+
previous: Number(previousPhLevel) || 0
|
|
58
|
+
};
|
|
59
|
+
}, [phLevel, previousPhLevel]);
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<section>
|
|
63
|
+
<Widget
|
|
64
|
+
title={t("Soil & Water Profile")}
|
|
65
|
+
loading={loading || outcomesLoading}
|
|
66
|
+
className="with-border-header h-w-btn-header"
|
|
67
|
+
>
|
|
68
|
+
<Stats
|
|
69
|
+
monitoredArea={monitoredArea}
|
|
70
|
+
predominantSoilType={predominantSoilType}
|
|
71
|
+
averagePhLevel={averagePhLevel}
|
|
72
|
+
t={t}
|
|
73
|
+
options={options}
|
|
74
|
+
/>
|
|
75
|
+
|
|
76
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '24px' }}>
|
|
77
|
+
<SoilType
|
|
78
|
+
soilTypeChart={soilType}
|
|
79
|
+
t={t}
|
|
80
|
+
options={options}
|
|
81
|
+
/>
|
|
82
|
+
|
|
83
|
+
<SalinityLevels
|
|
84
|
+
salinityLevelsChart={salinityLevels}
|
|
85
|
+
t={t}
|
|
86
|
+
options={options}
|
|
87
|
+
/>
|
|
88
|
+
|
|
89
|
+
<WaterQuality
|
|
90
|
+
waterQualityChart={waterQuality}
|
|
91
|
+
t={t}
|
|
92
|
+
options={options}
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
</Widget>
|
|
96
|
+
</section>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export default SoilWaterProfile;
|
|
101
|
+
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculate nice axis configuration with clean round numbers
|
|
3
|
+
* @param {Array} data - Chart data array
|
|
4
|
+
* @param {string} valueField - Field name containing the numeric values
|
|
5
|
+
* @param {number} multiplier - Multiplier for calculating max (e.g., 1.2 for 20% padding, 2 for double)
|
|
6
|
+
* @param {Object} defaultConfig - Default configuration when data is empty or all zeros
|
|
7
|
+
* @returns {Object} Axis configuration with min, max, and tickMethod
|
|
8
|
+
*/
|
|
9
|
+
export const calculateNiceAxisConfig = (data, valueField = 'value', multiplier = 1.2, defaultConfig = { min: 0, max: 10, tickMethod: () => [0, 2, 4, 6, 8, 10] }) => {
|
|
10
|
+
if (!data || data.length === 0) {
|
|
11
|
+
return defaultConfig;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const maxValue = Math.max(...data.map(item => item[valueField] || 0));
|
|
15
|
+
|
|
16
|
+
if (maxValue <= 0) {
|
|
17
|
+
return defaultConfig;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Calculate a nice round max value
|
|
21
|
+
const rawMax = maxValue * multiplier;
|
|
22
|
+
|
|
23
|
+
// Find appropriate tick interval that gives nice round numbers
|
|
24
|
+
const magnitude = Math.pow(10, Math.floor(Math.log10(rawMax)));
|
|
25
|
+
const possibleIntervals = [
|
|
26
|
+
magnitude * 0.1,
|
|
27
|
+
magnitude * 0.2,
|
|
28
|
+
magnitude * 0.5,
|
|
29
|
+
magnitude * 1,
|
|
30
|
+
magnitude * 2,
|
|
31
|
+
magnitude * 5,
|
|
32
|
+
magnitude * 10
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
// Pick the interval that gives us 4-6 ticks
|
|
36
|
+
let tickInterval = possibleIntervals[0];
|
|
37
|
+
for (const interval of possibleIntervals) {
|
|
38
|
+
const numTicks = Math.ceil(rawMax / interval);
|
|
39
|
+
if (numTicks >= 4 && numTicks <= 6) {
|
|
40
|
+
tickInterval = interval;
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const niceMax = Math.ceil(rawMax / tickInterval) * tickInterval;
|
|
46
|
+
const numTicks = Math.round(niceMax / tickInterval);
|
|
47
|
+
|
|
48
|
+
// Determine decimal places based on interval size
|
|
49
|
+
const decimalPlaces = tickInterval < 1 ? 1 : 0;
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
min: 0,
|
|
53
|
+
max: niceMax,
|
|
54
|
+
tickMethod: () => Array.from({ length: numTicks + 1 }, (_, i) => {
|
|
55
|
+
const tick = i * tickInterval;
|
|
56
|
+
// Round to avoid floating point precision issues
|
|
57
|
+
return Math.round(tick * Math.pow(10, decimalPlaces)) / Math.pow(10, decimalPlaces);
|
|
58
|
+
})
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Merge default categories with backend data
|
|
64
|
+
* Ensures all categories are displayed even if they have no data
|
|
65
|
+
* @param {Array} backendData - Data from backend
|
|
66
|
+
* @param {Array} defaultCategories - Default categories to always display
|
|
67
|
+
* @param {string} categoryKey - Key in backendData for category matching
|
|
68
|
+
* @param {string} valueKey - Key in backendData for value
|
|
69
|
+
* @param {string} labelKey - Key in defaultCategories for label
|
|
70
|
+
* @param {string} valueFieldKey - Key in defaultCategories for value matching
|
|
71
|
+
* @returns {Array} Merged data with all categories
|
|
72
|
+
*/
|
|
73
|
+
export const mergeDefaultCategories = (
|
|
74
|
+
backendData,
|
|
75
|
+
defaultCategories,
|
|
76
|
+
categoryKey = 'range',
|
|
77
|
+
valueKey = 'count',
|
|
78
|
+
labelKey = 'label',
|
|
79
|
+
valueFieldKey = 'value'
|
|
80
|
+
) => {
|
|
81
|
+
if (!defaultCategories || defaultCategories.length === 0) {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Create a map of backend data
|
|
86
|
+
const dataMap = new Map();
|
|
87
|
+
if (backendData && Array.isArray(backendData)) {
|
|
88
|
+
backendData.forEach(item => {
|
|
89
|
+
if (item?.[categoryKey]) {
|
|
90
|
+
dataMap.set(item[categoryKey], Number(item?.[valueKey]) || 0);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Merge with default categories, maintaining order from defaults
|
|
96
|
+
return defaultCategories.map(category => ({
|
|
97
|
+
label: category?.[labelKey] || category?.[valueFieldKey] || '',
|
|
98
|
+
value: dataMap.get(category?.[valueFieldKey]) ?? 0,
|
|
99
|
+
[categoryKey]: category?.[valueFieldKey] || ''
|
|
100
|
+
}));
|
|
101
|
+
};
|
|
102
|
+
|
|
@@ -2,8 +2,11 @@ import { DashboardLayout, Header } from '../../../../../../src/index.js'
|
|
|
2
2
|
import KeyInformation from './components/KeyInformation/index.jsx';
|
|
3
3
|
import MonitoringScopeAndFindings from './components/MonitoringScopeAndFindings/index.jsx';
|
|
4
4
|
import MangroveGrowth from './components/MangroveGrowth/index.jsx';
|
|
5
|
+
import BiodiversityHabitat from './components/BiodiversityHabitat/index.jsx';
|
|
6
|
+
import SoilWaterProfile from './components/SoilWaterProfile/index.jsx';
|
|
7
|
+
import AssociatedInformation from '../PlantingCycle/components/AssociatedInformation/index.jsx';
|
|
5
8
|
|
|
6
|
-
const MonitoringCampaignSummary = ({ header, activityData, loading = false, id, projectId, t = () => { }, getSummaryDetail, navigate, selectOptions
|
|
9
|
+
const MonitoringCampaignSummary = ({ header, activityData, loading = false, id, projectId, t = () => { }, getSummaryDetail, navigate, selectOptions, }) => {
|
|
7
10
|
return (
|
|
8
11
|
<DashboardLayout
|
|
9
12
|
header={
|
|
@@ -21,7 +24,10 @@ const MonitoringCampaignSummary = ({ header, activityData, loading = false, id,
|
|
|
21
24
|
>
|
|
22
25
|
<KeyInformation id={id} t={t} getSummaryDetail={getSummaryDetail} loading={loading} />
|
|
23
26
|
<MonitoringScopeAndFindings id={id} t={t} getSummaryDetail={getSummaryDetail} loading={loading} />
|
|
24
|
-
<MangroveGrowth id={id} t={t} getSummaryDetail={getSummaryDetail} loading={loading} />
|
|
27
|
+
<MangroveGrowth id={id} t={t} getSummaryDetail={getSummaryDetail} loading={loading} options={selectOptions} />
|
|
28
|
+
<BiodiversityHabitat id={id} t={t} getSummaryDetail={getSummaryDetail} loading={loading} options={selectOptions} />
|
|
29
|
+
<SoilWaterProfile id={id} t={t} getSummaryDetail={getSummaryDetail} loading={loading} options={selectOptions} />
|
|
30
|
+
<AssociatedInformation id={id} t={t} getSummaryDetail={getSummaryDetail} loading={loading} projectId={projectId} navigate={navigate} options={selectOptions} />
|
|
25
31
|
</DashboardLayout>
|
|
26
32
|
)
|
|
27
33
|
}
|