datastake-daf 0.6.810 → 0.6.811

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/components/index.js +1303 -1273
  2. package/dist/layouts/index.js +484 -460
  3. package/dist/pages/index.js +1518 -529
  4. package/dist/style/datastake/mapbox-gl.css +330 -0
  5. package/dist/utils/index.js +484 -460
  6. package/package.json +1 -1
  7. package/public/Vegetation/damage-from-insects-default.svg +2 -0
  8. package/public/Vegetation/dry-or-dead-default.svg +2 -0
  9. package/public/Vegetation/healthy-default.svg +2 -0
  10. package/public/Vegetation/yellowing.svg +2 -0
  11. package/src/@daf/core/components/Icon/configs/Droplets.js +9 -0
  12. package/src/@daf/core/components/Icon/configs/TrendUp.js +8 -0
  13. package/src/@daf/core/components/Icon/configs/index.js +4 -0
  14. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/BiodiversityHabitat/InvasiveSpecies.jsx +107 -0
  15. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/BiodiversityHabitat/ObservedFauna.jsx +80 -0
  16. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/BiodiversityHabitat/Stats.jsx +62 -0
  17. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/BiodiversityHabitat/index.jsx +109 -0
  18. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/KeyInformation/index.jsx +8 -4
  19. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MangroveGrowth/PlantedSpecies.jsx +21 -9
  20. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MangroveGrowth/SeedlingsHeight.jsx +36 -2
  21. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MangroveGrowth/Stats.jsx +3 -3
  22. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MangroveGrowth/VegetationHealth.jsx +19 -8
  23. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MangroveGrowth/index.jsx +13 -9
  24. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/SoilWaterProfile/SalinityLevels.jsx +100 -0
  25. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/SoilWaterProfile/SoilType.jsx +84 -0
  26. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/SoilWaterProfile/Stats.jsx +72 -0
  27. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/SoilWaterProfile/WaterQuality.jsx +84 -0
  28. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/SoilWaterProfile/index.jsx +101 -0
  29. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/chartHelpers.js +102 -0
  30. package/src/@daf/pages/Summary/Activities/MonitoringCampaign/index.jsx +8 -2
  31. package/src/@daf/utils/numbers.js +1 -1
@@ -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}/outcomes`,
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
- vegetationHealthChart,
48
- seedlingsHeightChart,
49
- plantedSpeciesChart
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={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={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={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
+
@@ -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
+
@@ -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
  }
@@ -50,7 +50,7 @@ export const calculateStatChange = (data, options = {}) => {
50
50
  }
51
51
 
52
52
  const { current, previous } = data;
53
-
53
+
54
54
  // Validate that both values are numbers
55
55
  if (typeof current !== 'number' || typeof previous !== 'number') {
56
56
  return null;