datastake-daf 0.6.784 → 0.6.785
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 +99 -99
- package/dist/pages/index.js +846 -65
- package/dist/style/datastake/mapbox-gl.css +330 -0
- package/dist/utils/index.js +58 -0
- package/package.json +1 -1
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/KeyInformation/index.jsx +48 -0
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MangroveGrowth/PlantedSpecies.jsx +73 -0
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MangroveGrowth/SeedlingsHeight.jsx +44 -0
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MangroveGrowth/Stats.jsx +86 -0
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MangroveGrowth/VegetationHealth.jsx +73 -0
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MangroveGrowth/index.jsx +92 -0
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MonitoringScopeAndFindings/index.jsx +348 -0
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/config.js +35 -0
- package/src/@daf/pages/Summary/Activities/MonitoringCampaign/index.jsx +30 -0
- package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CommunityParticipation/CommunityStats/helper.js +1 -1
- package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CycleIndicators/index.jsx +1 -1
- package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CycleOutcomes/index.jsx +1 -1
- package/src/@daf/pages/Summary/Activities/PlantingCycle/helper.js +0 -56
- package/src/@daf/utils/numbers.js +57 -0
- package/src/pages.js +1 -0
- package/src/utils.js +1 -1
|
@@ -0,0 +1,73 @@
|
|
|
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 = ['#016C6E', '#F5C2AC', '#F0A888', '#DF571E', '#C04B19', '#9B3D14', '#7A2F0F'];
|
|
6
|
+
|
|
7
|
+
const VegetationHealth = ({
|
|
8
|
+
vegetationHealthChart,
|
|
9
|
+
t = (s) => s
|
|
10
|
+
}) => {
|
|
11
|
+
const pieData = useMemo(() => {
|
|
12
|
+
const data = vegetationHealthChart || [];
|
|
13
|
+
const total = data.reduce((sum, item) => sum + (Number(item?.value) || 0), 0);
|
|
14
|
+
|
|
15
|
+
return data.map((item, index) => ({
|
|
16
|
+
value: Number(item?.value) || 0,
|
|
17
|
+
percent: total > 0 ? (Number(item?.value) || 0) / total : 0,
|
|
18
|
+
color: COLORS[index % COLORS.length],
|
|
19
|
+
label: item?.type || '',
|
|
20
|
+
key: item?.type || `item-${index}`,
|
|
21
|
+
}));
|
|
22
|
+
}, [vegetationHealthChart]);
|
|
23
|
+
|
|
24
|
+
const isEmpty = useMemo(() => {
|
|
25
|
+
return !vegetationHealthChart || vegetationHealthChart.length === 0 ||
|
|
26
|
+
vegetationHealthChart.every(item => !item?.value || Number(item.value) === 0);
|
|
27
|
+
}, [vegetationHealthChart]);
|
|
28
|
+
|
|
29
|
+
const getTooltipChildren = useCallback(
|
|
30
|
+
(item) => {
|
|
31
|
+
if (isEmpty) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return renderTooltipJsx({
|
|
36
|
+
title: t("Vegetation Health"),
|
|
37
|
+
items: [
|
|
38
|
+
{
|
|
39
|
+
color: item.color,
|
|
40
|
+
label: item.label || '',
|
|
41
|
+
value: `${item.value || 0}%`,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
[t, isEmpty]
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Widget
|
|
51
|
+
title={t("Vegetation Health")}
|
|
52
|
+
className="with-border-header h-w-btn-header"
|
|
53
|
+
>
|
|
54
|
+
<div className="flex flex-1 flex-column justify-content-center">
|
|
55
|
+
<div className="flex justify-content-center w-full">
|
|
56
|
+
<PieChart
|
|
57
|
+
data={pieData}
|
|
58
|
+
isPie
|
|
59
|
+
isEmpty={isEmpty}
|
|
60
|
+
getTooltipChildren={getTooltipChildren}
|
|
61
|
+
mouseXOffset={10}
|
|
62
|
+
mouseYOffset={10}
|
|
63
|
+
changeOpacityOnHover={false}
|
|
64
|
+
doConstraints={false}
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</Widget>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export default VegetationHealth;
|
|
73
|
+
|
package/src/@daf/pages/Summary/Activities/MonitoringCampaign/components/MangroveGrowth/index.jsx
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { Widget } from '../../../../../../../../src/index.js';
|
|
3
|
+
import { useWidgetFetch } from '../../../../../../hooks/useWidgetFetch.js';
|
|
4
|
+
import VegetationHealth from './VegetationHealth.jsx';
|
|
5
|
+
import SeedlingsHeight from './SeedlingsHeight.jsx';
|
|
6
|
+
import PlantedSpecies from './PlantedSpecies.jsx';
|
|
7
|
+
import Stats from './Stats.jsx';
|
|
8
|
+
|
|
9
|
+
const MangroveGrowth = ({
|
|
10
|
+
id,
|
|
11
|
+
getSummaryDetail,
|
|
12
|
+
loading = false,
|
|
13
|
+
t = (s) => s
|
|
14
|
+
}) => {
|
|
15
|
+
const defaultConfig = useMemo(
|
|
16
|
+
() => ({
|
|
17
|
+
basepath: "events/monitoring-campaign",
|
|
18
|
+
url: `/summary/${id}/outcomes`,
|
|
19
|
+
stop: !id,
|
|
20
|
+
}),
|
|
21
|
+
[id],
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const customGetData = useMemo(() => {
|
|
25
|
+
if (getSummaryDetail && id) {
|
|
26
|
+
return ({ url, params = {} }) => {
|
|
27
|
+
const match = url.match(/\/summary\/[^/]+\/(.+)/);
|
|
28
|
+
if (match) {
|
|
29
|
+
const [, type] = match;
|
|
30
|
+
return getSummaryDetail(id, type, params);
|
|
31
|
+
}
|
|
32
|
+
throw new Error(`Invalid URL format: ${url}`);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}, [getSummaryDetail, id]);
|
|
37
|
+
|
|
38
|
+
const { loading: outcomesLoading, data: outcomesData } = useWidgetFetch({
|
|
39
|
+
config: defaultConfig,
|
|
40
|
+
getData: customGetData
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const {
|
|
44
|
+
survivalRate,
|
|
45
|
+
averageHeight,
|
|
46
|
+
averageDiameter,
|
|
47
|
+
vegetationHealthChart,
|
|
48
|
+
seedlingsHeightChart,
|
|
49
|
+
plantedSpeciesChart
|
|
50
|
+
} = outcomesData || {};
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<section>
|
|
54
|
+
<Widget
|
|
55
|
+
title={t("Mangrove Growth")}
|
|
56
|
+
loading={loading || outcomesLoading}
|
|
57
|
+
className="with-border-header h-w-btn-header"
|
|
58
|
+
>
|
|
59
|
+
<Stats
|
|
60
|
+
survivalRate={survivalRate}
|
|
61
|
+
averageHeight={averageHeight}
|
|
62
|
+
averageDiameter={averageDiameter}
|
|
63
|
+
t={t}
|
|
64
|
+
/>
|
|
65
|
+
|
|
66
|
+
<div style={{ display: "flex", gap: "24px" }}>
|
|
67
|
+
<section style={{ flex: 1 }}>
|
|
68
|
+
<VegetationHealth
|
|
69
|
+
vegetationHealthChart={vegetationHealthChart}
|
|
70
|
+
t={t}
|
|
71
|
+
/>
|
|
72
|
+
</section>
|
|
73
|
+
<section style={{ flex: 1 }}>
|
|
74
|
+
<SeedlingsHeight
|
|
75
|
+
seedlingsHeightChart={seedlingsHeightChart}
|
|
76
|
+
t={t}
|
|
77
|
+
/>
|
|
78
|
+
</section>
|
|
79
|
+
<section style={{ flex: 1 }}>
|
|
80
|
+
<PlantedSpecies
|
|
81
|
+
plantedSpeciesChart={plantedSpeciesChart}
|
|
82
|
+
t={t}
|
|
83
|
+
/>
|
|
84
|
+
</section>
|
|
85
|
+
</div>
|
|
86
|
+
</Widget>
|
|
87
|
+
</section>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export default MangroveGrowth;
|
|
92
|
+
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import React, { useMemo, useState } from 'react';
|
|
2
|
+
import { Widget, MineSiteMap } from '../../../../../../../../src/index.js';
|
|
3
|
+
import { useWidgetFetch } from '../../../../../../hooks/useWidgetFetch.js';
|
|
4
|
+
import { convertDMS } from '../../../../../../../../src/helpers/Map';
|
|
5
|
+
import CustomIcon from '../../../../../../../../src/@daf/core/components/Icon/CustomIcon.jsx';
|
|
6
|
+
import { renderDateFormatted } from '../../../../../../../../src/helpers/Forms';
|
|
7
|
+
|
|
8
|
+
const VISITS_TAB = "visits";
|
|
9
|
+
const GROWTH_AND_SURVIVAL_TAB = "growthAndSurvival";
|
|
10
|
+
const FAUNA_SIGHTINGS_TAB = "faunaSightings";
|
|
11
|
+
const INVASIVE_SPECIES_TAB = "invasiveSpecies";
|
|
12
|
+
const SYMPTOM_HOTSPOTS_TAB = "symptomHotspots";
|
|
13
|
+
const SOIL_TAB = "soil";
|
|
14
|
+
|
|
15
|
+
const MonitoringScopeAndFindings = ({
|
|
16
|
+
id,
|
|
17
|
+
getSummaryDetail,
|
|
18
|
+
loading = false,
|
|
19
|
+
t = (s) => s
|
|
20
|
+
}) => {
|
|
21
|
+
const [activeTab, setActiveTab] = useState(VISITS_TAB);
|
|
22
|
+
|
|
23
|
+
const defaultConfig = useMemo(
|
|
24
|
+
() => ({
|
|
25
|
+
basepath: "events/monitoring-campaign",
|
|
26
|
+
url: `/summary/${id}/monitoring-scope`,
|
|
27
|
+
stop: !id,
|
|
28
|
+
}),
|
|
29
|
+
[id],
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const customGetData = useMemo(() => {
|
|
33
|
+
if (getSummaryDetail && id) {
|
|
34
|
+
return ({ url, params = {} }) => {
|
|
35
|
+
const match = url.match(/\/summary\/[^/]+\/(.+)/);
|
|
36
|
+
if (match) {
|
|
37
|
+
const [, type] = match;
|
|
38
|
+
return getSummaryDetail(id, type, { ...params, tab: activeTab });
|
|
39
|
+
}
|
|
40
|
+
throw new Error(`Invalid URL format: ${url}`);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
}, [getSummaryDetail, id, activeTab]);
|
|
45
|
+
|
|
46
|
+
const { loading: monitoringScopeLoading, data: monitoringScopeData } = useWidgetFetch({
|
|
47
|
+
config: defaultConfig,
|
|
48
|
+
getData: customGetData
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const filtersConfig = useMemo(() => {
|
|
52
|
+
switch (activeTab) {
|
|
53
|
+
case GROWTH_AND_SURVIVAL_TAB:
|
|
54
|
+
return [
|
|
55
|
+
{
|
|
56
|
+
label: t("Mangrove Survival Rate"),
|
|
57
|
+
placeholder: t("Select"),
|
|
58
|
+
key: "mangroveSurvivalRate",
|
|
59
|
+
type: "slider",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
label: t("Planting Density"),
|
|
63
|
+
placeholder: t("Select"),
|
|
64
|
+
key: "plantingDensity",
|
|
65
|
+
type: "select",
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
case FAUNA_SIGHTINGS_TAB:
|
|
69
|
+
return [
|
|
70
|
+
{
|
|
71
|
+
label: t("Fauna Observed"),
|
|
72
|
+
placeholder: t("Select"),
|
|
73
|
+
key: "faunaSightings",
|
|
74
|
+
type: "select",
|
|
75
|
+
options: [
|
|
76
|
+
{ label: t("Birds"), value: "birds" },
|
|
77
|
+
{ label: t("Crabs"), value: "crabs" },
|
|
78
|
+
{ label: t("Fish"), value: "fish" },
|
|
79
|
+
{ label: t("Molluscs"), value: "molluscs" },
|
|
80
|
+
{ label: t("Oysters"), value: "oysters" },
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
case INVASIVE_SPECIES_TAB:
|
|
85
|
+
return [
|
|
86
|
+
{
|
|
87
|
+
label: t("Fauna Observed"),
|
|
88
|
+
placeholder: t("Select"),
|
|
89
|
+
key: "invasiveSpecies",
|
|
90
|
+
type: "select",
|
|
91
|
+
options: [
|
|
92
|
+
{ label: t("Spiders"), value: "spiders" },
|
|
93
|
+
{ label: t("Scale insects"), value: "scaleInsects" },
|
|
94
|
+
{ label: t("Caterpillars"), value: "caterpillars" },
|
|
95
|
+
{ label: t("Unidentified pests"), value: "unidentifiedPests" },
|
|
96
|
+
{ label: t("Other"), value: "other" },
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
case SYMPTOM_HOTSPOTS_TAB:
|
|
101
|
+
return [
|
|
102
|
+
{
|
|
103
|
+
label: t("Symptom Hotspots"),
|
|
104
|
+
placeholder: t("Select"),
|
|
105
|
+
key: "symptomHotspots",
|
|
106
|
+
type: "select",
|
|
107
|
+
options: [
|
|
108
|
+
{ label: t("Reddish spots on leaves"), value: "reddishSpotsOnLeaves" },
|
|
109
|
+
{ label: t("Black spots on leaves"), value: "blackSpotsOnLeaves" },
|
|
110
|
+
{ label: t("Yellowing of leaves"), value: "yellowingOfLeaves" },
|
|
111
|
+
{ label: t("Presence of mosaic"), value: "presenceOfMosaic" },
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
case SOIL_TAB:
|
|
116
|
+
return [
|
|
117
|
+
{
|
|
118
|
+
label: t("Soil"),
|
|
119
|
+
placeholder: t("Select"),
|
|
120
|
+
key: "soil",
|
|
121
|
+
type: "select",
|
|
122
|
+
options: [
|
|
123
|
+
{ label: t("Sandy"), value: "sandy" },
|
|
124
|
+
{ label: t("Clay"), value: "clay" },
|
|
125
|
+
{ label: t("Muddy"), value: "muddy" },
|
|
126
|
+
{ label: t("Loamy"), value: "loamy" },
|
|
127
|
+
{ label: t("Mixed"), value: "mixed" },
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
default:
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
}, [activeTab, t]);
|
|
135
|
+
|
|
136
|
+
const mappedData = useMemo(() => {
|
|
137
|
+
if (!monitoringScopeData || !monitoringScopeData.plots) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const { plots = [], monitoringActivities = [] } = monitoringScopeData;
|
|
142
|
+
|
|
143
|
+
if (activeTab === VISITS_TAB) {
|
|
144
|
+
return plots.map((plot, index) => {
|
|
145
|
+
const area = plot?.perimeter ? plot.perimeter.map(coord =>
|
|
146
|
+
Array.isArray(coord) && coord.length >= 2
|
|
147
|
+
? [coord[1], coord[0]]
|
|
148
|
+
: coord
|
|
149
|
+
) : null;
|
|
150
|
+
|
|
151
|
+
const validArea = area && Array.isArray(area) && area.length >= 3 ? area : null;
|
|
152
|
+
|
|
153
|
+
const matchingActivity = monitoringActivities?.find(activity =>
|
|
154
|
+
activity.plotId === plot.id || activity.plotId === plot._id
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const gps = matchingActivity?.locationCheckArrival ? {
|
|
158
|
+
latitude: matchingActivity.locationCheckArrival.latitude,
|
|
159
|
+
longitude: matchingActivity.locationCheckArrival.longitude
|
|
160
|
+
} : null;
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
_id: plot._id || {},
|
|
164
|
+
area: validArea,
|
|
165
|
+
color: "#15FFFFB2",
|
|
166
|
+
datastakeId: plot.datastakeId || `PLOT-${String(index + 1).padStart(9, '0')}`,
|
|
167
|
+
gps: gps,
|
|
168
|
+
id: plot.id || plot._id || `plot-${index}`,
|
|
169
|
+
name: plot.name || t("Plot"),
|
|
170
|
+
date: matchingActivity?.date,
|
|
171
|
+
subTitle: matchingActivity?.date
|
|
172
|
+
? renderDateFormatted(matchingActivity.date, "DD MMM YY")
|
|
173
|
+
: plot.name,
|
|
174
|
+
plotName: plot.name,
|
|
175
|
+
territoryTitle: plot.name,
|
|
176
|
+
type: plot.type || 'Operational Plot',
|
|
177
|
+
lastVisit: matchingActivity?.date,
|
|
178
|
+
implementer: matchingActivity?.implementer,
|
|
179
|
+
areaHa: plot.area,
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return plots.map((plot, index) => {
|
|
185
|
+
const area = plot?.perimeter ? plot.perimeter.map(coord =>
|
|
186
|
+
Array.isArray(coord) && coord.length >= 2
|
|
187
|
+
? [coord[1], coord[0]]
|
|
188
|
+
: coord
|
|
189
|
+
) : null;
|
|
190
|
+
|
|
191
|
+
const validArea = area && Array.isArray(area) && area.length >= 3 ? area : null;
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
_id: plot._id || {},
|
|
195
|
+
area: validArea,
|
|
196
|
+
color: "#15FFFFB2",
|
|
197
|
+
datastakeId: plot.datastakeId || `PLOT-${String(index + 1).padStart(9, '0')}`,
|
|
198
|
+
id: plot.id || plot._id || `plot-${index}`,
|
|
199
|
+
name: plot.name || t("Plot"),
|
|
200
|
+
plotName: plot.name,
|
|
201
|
+
territoryTitle: plot.name,
|
|
202
|
+
type: plot.type || 'Operational Plot',
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
}, [monitoringScopeData, activeTab, t]);
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<section>
|
|
209
|
+
<Widget
|
|
210
|
+
title={t("Monitoring Scope & Findings")}
|
|
211
|
+
className="v2-widget no-px no-p-body h-w-btn-header with-border-header"
|
|
212
|
+
style={{ height: '100%', display: 'flex', flexDirection: 'column' }}
|
|
213
|
+
tabsConfig={{
|
|
214
|
+
tabs: [
|
|
215
|
+
{
|
|
216
|
+
label: t("Visits"),
|
|
217
|
+
value: VISITS_TAB,
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
label: t("Growth & Survival"),
|
|
221
|
+
value: GROWTH_AND_SURVIVAL_TAB,
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
label: t("Fauna Sightings"),
|
|
225
|
+
value: FAUNA_SIGHTINGS_TAB,
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
label: t("Invasive Species"),
|
|
229
|
+
value: INVASIVE_SPECIES_TAB,
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
label: t("Symptom Hotspots"),
|
|
233
|
+
value: SYMPTOM_HOTSPOTS_TAB,
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
label: t("Soil"),
|
|
237
|
+
value: SOIL_TAB,
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
value: activeTab,
|
|
241
|
+
onChange: setActiveTab,
|
|
242
|
+
}}
|
|
243
|
+
>
|
|
244
|
+
<MineSiteMap
|
|
245
|
+
data={mappedData}
|
|
246
|
+
link={false}
|
|
247
|
+
style={{ height: '100%', width: '100%' }}
|
|
248
|
+
maxZoom={18}
|
|
249
|
+
isSatellite={true}
|
|
250
|
+
onClickLink={() => { }}
|
|
251
|
+
onFilterChange={() => { }}
|
|
252
|
+
primaryLink
|
|
253
|
+
showSider={false}
|
|
254
|
+
filtersConfig={filtersConfig}
|
|
255
|
+
renderTooltipForLocation={(data) => {
|
|
256
|
+
if (activeTab === VISITS_TAB && data.gps) {
|
|
257
|
+
const coordinates = data.gps?.latitude && data.gps?.longitude
|
|
258
|
+
? convertDMS(data.gps.latitude, data.gps.longitude)
|
|
259
|
+
: null;
|
|
260
|
+
|
|
261
|
+
if (!coordinates) {
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const iconColor = "#016C6E";
|
|
266
|
+
|
|
267
|
+
const tooltipItems = [
|
|
268
|
+
{
|
|
269
|
+
label: t("Coordinates"),
|
|
270
|
+
value: (
|
|
271
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', flexWrap: 'nowrap' }}>
|
|
272
|
+
<div style={{ display: 'flex', alignItems: 'center' }}>
|
|
273
|
+
<CustomIcon
|
|
274
|
+
name="SpacingHeight"
|
|
275
|
+
width={14}
|
|
276
|
+
height={14}
|
|
277
|
+
color={iconColor}
|
|
278
|
+
/>
|
|
279
|
+
<span style={{ fontWeight: 600, marginLeft: '4px' }}>{coordinates[0]}</span>
|
|
280
|
+
</div>
|
|
281
|
+
<div style={{ display: 'flex', alignItems: 'center' }}>
|
|
282
|
+
<CustomIcon
|
|
283
|
+
name="SpacingWidth"
|
|
284
|
+
width={14}
|
|
285
|
+
height={14}
|
|
286
|
+
color={iconColor}
|
|
287
|
+
/>
|
|
288
|
+
<span style={{ fontWeight: 600, marginLeft: '4px' }}>{coordinates[1]}</span>
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
),
|
|
292
|
+
},
|
|
293
|
+
];
|
|
294
|
+
|
|
295
|
+
if (data.date) {
|
|
296
|
+
tooltipItems.push({
|
|
297
|
+
label: t("Date"),
|
|
298
|
+
value: renderDateFormatted(data.date, "DD MMM YY"),
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (data.implementer) {
|
|
303
|
+
tooltipItems.push({
|
|
304
|
+
label: t("Implementer"),
|
|
305
|
+
value: data.implementer,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return tooltipItems;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return [];
|
|
313
|
+
}}
|
|
314
|
+
renderTooltipForTerritory={(data) => {
|
|
315
|
+
const items = [
|
|
316
|
+
{
|
|
317
|
+
label: t("Plot Name"),
|
|
318
|
+
value: data.plotName || data.name || "--",
|
|
319
|
+
},
|
|
320
|
+
];
|
|
321
|
+
|
|
322
|
+
if (activeTab === VISITS_TAB && data.lastVisit) {
|
|
323
|
+
items.push({
|
|
324
|
+
label: t("Last visit"),
|
|
325
|
+
value: renderDateFormatted(data.lastVisit, "DD MMM YY"),
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (activeTab === VISITS_TAB && data.areaHa) {
|
|
330
|
+
items.push({
|
|
331
|
+
label: t("Area"),
|
|
332
|
+
value: `${data.areaHa} ha`,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return items;
|
|
337
|
+
}}
|
|
338
|
+
renderTooltipTags={() => { }}
|
|
339
|
+
type="location-territory"
|
|
340
|
+
loading={loading || monitoringScopeLoading}
|
|
341
|
+
/>
|
|
342
|
+
</Widget>
|
|
343
|
+
</section>
|
|
344
|
+
);
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
export default MonitoringScopeAndFindings;
|
|
348
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export const getKeyIndicatorsRowConfig = ({ t, data = {} }) => [
|
|
4
|
+
{
|
|
5
|
+
label: t('Region'),
|
|
6
|
+
render: () => {
|
|
7
|
+
return <div>{ data?.region || '-'}</div>;
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
label: t('Associated Plots'),
|
|
12
|
+
render: () => {
|
|
13
|
+
return <div>{data?.associatedPlotsCount || '0'}</div>
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
label: t('Implementation Partners'),
|
|
18
|
+
render: () => {
|
|
19
|
+
return <div>{data?.partnersCount || '0'}</div>
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
label: t('Total Activities'),
|
|
24
|
+
render: () => {
|
|
25
|
+
return <div>{data?.activitiesCount || '0'}</div>
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
label: t('Information Sources'),
|
|
30
|
+
render: () => {
|
|
31
|
+
return <div>{data?.informationSourcesCount || '0'}</div>
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DashboardLayout, Header } from '../../../../../../src/index.js'
|
|
2
|
+
import KeyInformation from './components/KeyInformation/index.jsx';
|
|
3
|
+
import MonitoringScopeAndFindings from './components/MonitoringScopeAndFindings/index.jsx';
|
|
4
|
+
import MangroveGrowth from './components/MangroveGrowth/index.jsx';
|
|
5
|
+
|
|
6
|
+
const MonitoringCampaignSummary = ({ header, activityData, loading = false, id, projectId, t = () => { }, getSummaryDetail, navigate, selectOptions }) => {
|
|
7
|
+
return (
|
|
8
|
+
<DashboardLayout
|
|
9
|
+
header={
|
|
10
|
+
<Header
|
|
11
|
+
title={header?.title + ' Summary' || ''}
|
|
12
|
+
supportText={header?.supportText || ''}
|
|
13
|
+
onDownload={header?.onDownload}
|
|
14
|
+
downloadDisabled={header?.downloadDisabled}
|
|
15
|
+
actionButtons={header?.actionButtons}
|
|
16
|
+
breadcrumbs={header?.breadcrumbs}
|
|
17
|
+
goBackTo={header?.goBackTo}
|
|
18
|
+
loading={header?.loading}
|
|
19
|
+
/>
|
|
20
|
+
}
|
|
21
|
+
>
|
|
22
|
+
<KeyInformation id={id} t={t} getSummaryDetail={getSummaryDetail} loading={loading} />
|
|
23
|
+
<MonitoringScopeAndFindings id={id} t={t} getSummaryDetail={getSummaryDetail} loading={loading} />
|
|
24
|
+
<MangroveGrowth id={id} t={t} getSummaryDetail={getSummaryDetail} loading={loading} />
|
|
25
|
+
</DashboardLayout>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default MonitoringCampaignSummary;
|
|
30
|
+
|
package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CycleIndicators/index.jsx
CHANGED
|
@@ -3,7 +3,7 @@ import { Widget, StatCard } from '../../../../../../../index.js';
|
|
|
3
3
|
import CyclePartners from './CyclePartners/index.jsx';
|
|
4
4
|
import HealthAndSafety from './HealthAndSafety/index.jsx';
|
|
5
5
|
import { useWidgetFetch } from '../../../../../../hooks/useWidgetFetch.js';
|
|
6
|
-
import { calculateStatChange } from '
|
|
6
|
+
import { calculateStatChange } from '../../../../../../utils/numbers.js';
|
|
7
7
|
|
|
8
8
|
const CycleIndicators = ({
|
|
9
9
|
id,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
2
|
import { Widget, StatCard } from '../../../../../../../../src/index.js';
|
|
3
3
|
import { useWidgetFetch } from '../../../../../../hooks/useWidgetFetch.js';
|
|
4
|
-
import { calculateStatChange } from '
|
|
4
|
+
import { calculateStatChange } from '../../../../../../utils/numbers.js';
|
|
5
5
|
import RestoredArea from './RestoredArea.jsx';
|
|
6
6
|
import PlantingActivitiesTimeline from './PlantingActivitiesTimeline.jsx';
|
|
7
7
|
|
|
@@ -281,60 +281,4 @@ export const getHealthAndSafetyTooltipChildren = (item, isEmpty, healthAndSafety
|
|
|
281
281
|
});
|
|
282
282
|
};
|
|
283
283
|
|
|
284
|
-
/**
|
|
285
|
-
* Calculates stat change object for StatCard component based on current and previous values
|
|
286
|
-
* @param {Object} data - Object with current and previous values
|
|
287
|
-
* @param {number} data.current - Current value
|
|
288
|
-
* @param {number} data.previous - Previous value
|
|
289
|
-
* @param {Object} options - Optional configuration
|
|
290
|
-
* @param {string} options.tooltipText - Custom tooltip text
|
|
291
|
-
* @param {string} options.format - Format type: 'percentage' (default) or 'absolute'
|
|
292
|
-
* @param {number} options.decimalPlaces - Number of decimal places for percentage (default: 1)
|
|
293
|
-
* @returns {Object|null} Change object for StatCard or null if data is invalid
|
|
294
|
-
*/
|
|
295
|
-
export const calculateStatChange = (data, options = {}) => {
|
|
296
|
-
if (!data || typeof data !== 'object') {
|
|
297
|
-
return null;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
const { current, previous } = data;
|
|
301
|
-
|
|
302
|
-
// Validate that both values are numbers
|
|
303
|
-
if (typeof current !== 'number' || typeof previous !== 'number') {
|
|
304
|
-
return null;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// If previous is 0, we can't calculate percentage change
|
|
308
|
-
if (previous === 0) {
|
|
309
|
-
return null;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const {
|
|
313
|
-
tooltipText,
|
|
314
|
-
format = 'percentage',
|
|
315
|
-
decimalPlaces = 1,
|
|
316
|
-
} = options;
|
|
317
|
-
|
|
318
|
-
// Calculate the difference
|
|
319
|
-
const difference = current - previous;
|
|
320
|
-
const isPositive = difference >= 0;
|
|
321
|
-
const direction = isPositive ? 'up' : 'down';
|
|
322
|
-
|
|
323
|
-
// Format the value
|
|
324
|
-
let value;
|
|
325
|
-
if (format === 'absolute') {
|
|
326
|
-
// Show absolute difference
|
|
327
|
-
value = Math.abs(difference).toLocaleString();
|
|
328
|
-
} else {
|
|
329
|
-
// Show percentage change
|
|
330
|
-
const percentageChange = (Math.abs(difference) / previous) * 100;
|
|
331
|
-
value = `${percentageChange.toFixed(decimalPlaces)}%`;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
return {
|
|
335
|
-
value,
|
|
336
|
-
direction,
|
|
337
|
-
tooltipText: tooltipText || undefined,
|
|
338
|
-
};
|
|
339
|
-
};
|
|
340
284
|
|