datastake-daf 0.6.786 → 0.6.788
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 +226 -91
- package/dist/pages/index.js +774 -33
- package/dist/style/datastake/mapbox-gl.css +330 -0
- package/package.json +1 -1
- package/src/@daf/core/components/Dashboard/Widget/ActivityIndicators/ActivityIndicators.stories.js +24 -0
- package/src/@daf/core/components/Dashboard/Widget/ActivityIndicators/index.jsx +1 -0
- package/src/@daf/core/components/Dashboard/Widget/ActivityIndicators/style.js +34 -0
- package/src/@daf/core/components/Dashboard/Widget/KeyIndicators/KeyIndicators.stories.js +39 -0
- package/src/@daf/core/components/Dashboard/Widget/KeyIndicators/LabelWithIcon.jsx +38 -0
- package/src/@daf/core/components/Dashboard/Widget/KeyIndicators/index.jsx +16 -3
- package/src/@daf/core/components/Dashboard/Widget/KeyIndicators/style.js +33 -0
- package/src/@daf/pages/Summary/Activities/Monitoring/components/ActivityImagery/index.jsx +29 -0
- package/src/@daf/pages/Summary/Activities/Monitoring/components/ActivityLocation/index.jsx +94 -0
- package/src/@daf/pages/Summary/Activities/Monitoring/components/MangroveGrowthAndSurvival/components/PlantedSpecies/index.jsx +56 -0
- package/src/@daf/pages/Summary/Activities/Monitoring/components/MangroveGrowthAndSurvival/components/SeedlingsHeight/index.jsx +121 -0
- package/src/@daf/pages/Summary/Activities/Monitoring/components/MangroveGrowthAndSurvival/components/SurvivalRate/index.jsx +94 -0
- package/src/@daf/pages/Summary/Activities/Monitoring/components/MangroveGrowthAndSurvival/index.jsx +54 -0
- package/src/@daf/pages/Summary/Activities/Monitoring/components/WorkersDistribution/index.jsx +49 -0
- package/src/@daf/pages/Summary/Activities/Monitoring/config.js +51 -0
- package/src/@daf/pages/Summary/Activities/Monitoring/helper.js +236 -0
- package/src/@daf/pages/Summary/Activities/Monitoring/index.jsx +66 -0
- package/src/pages.js +1 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import L from "leaflet";
|
|
2
|
+
|
|
3
|
+
const normalizeUrl = (url) => url?.endsWith(':') ? url.slice(0, -1) : url;
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export const extractFromPhotoDoc = (photoDoc, label, docIndex) => {
|
|
7
|
+
const photos = photoDoc?.pictures?.filter(p => p?.url) || (photoDoc?.url ? [photoDoc] : []);
|
|
8
|
+
|
|
9
|
+
return photos.map((photo, photoIndex) => ({
|
|
10
|
+
src: normalizeUrl(photo.url),
|
|
11
|
+
alt: photo.name || photo.alt || `${label} - Image ${docIndex + 1}-${photoIndex + 1}`
|
|
12
|
+
}));
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const getActivityImages = (activityData) => {
|
|
16
|
+
const photoArrays = [
|
|
17
|
+
{ data: activityData?.groupPhotos, label: 'Group Photo' },
|
|
18
|
+
{ data: activityData?.photosStartActivity, label: 'Start of Activity' },
|
|
19
|
+
{ data: activityData?.photosDuringActivity, label: 'During Activity' },
|
|
20
|
+
{ data: activityData?.photosEndActivity, label: 'End of Activity' }
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
return photoArrays
|
|
24
|
+
.flatMap(({ data, label }) =>
|
|
25
|
+
Array.isArray(data)
|
|
26
|
+
? data.flatMap((photoDoc, index) => extractFromPhotoDoc(photoDoc, label, index))
|
|
27
|
+
: []
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const GENDER_COLORS = ['#016C6E', '#00AEB1'];
|
|
32
|
+
|
|
33
|
+
export const getGenderDistributionData = (activityData) => {
|
|
34
|
+
return {
|
|
35
|
+
Male: activityData?.genderDistributionMale || 0,
|
|
36
|
+
Female: activityData?.genderDistributionFemale || 0,
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
export const isGenderDistributionEmpty = (genderDistributionData) => {
|
|
42
|
+
return Object.values(genderDistributionData).every(val => !val || val === 0);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const calculateGenderPieData = (genderDistributionData) => {
|
|
46
|
+
const total = Object.values(genderDistributionData).reduce((all, val) => all + (val || 0), 0);
|
|
47
|
+
|
|
48
|
+
return Object.keys(genderDistributionData).map((key, index) => {
|
|
49
|
+
const color = GENDER_COLORS[index % GENDER_COLORS.length];
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
value: genderDistributionData[key] || 0,
|
|
53
|
+
percent: total > 0 ? (genderDistributionData[key] || 0) / total : 0,
|
|
54
|
+
color: color,
|
|
55
|
+
label: key,
|
|
56
|
+
key: key,
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const getGenderTooltipChildren = (item, isEmpty, genderDistributionData, t, renderTooltipJsx) => {
|
|
62
|
+
if (isEmpty) {
|
|
63
|
+
if (!Object.keys(genderDistributionData).length) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return renderTooltipJsx({
|
|
68
|
+
title: t("Gender"),
|
|
69
|
+
items: Object.keys(genderDistributionData).map((k) => ({
|
|
70
|
+
label: k,
|
|
71
|
+
value: 0,
|
|
72
|
+
})),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return renderTooltipJsx({
|
|
77
|
+
title: t("Gender"),
|
|
78
|
+
items: [
|
|
79
|
+
{
|
|
80
|
+
color: item.color,
|
|
81
|
+
label: t(item.label),
|
|
82
|
+
value: `${item.value || 0}`,
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const getIndicatorType = (value) => {
|
|
89
|
+
if (value === "yes" || value === true) return "compliant";
|
|
90
|
+
if (value === "no" || value === false) return "notCompliant";
|
|
91
|
+
if (value === null || value === undefined) return "empty";
|
|
92
|
+
return "empty"; // default fallback
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const getActivityIndicatorsConfig = (activityData, t) => {
|
|
96
|
+
|
|
97
|
+
return [
|
|
98
|
+
{ icon: "Aid", label: t("Aid kit availability"), type: getIndicatorType(activityData?.aidKitAccessible) },
|
|
99
|
+
{ icon: "MineOperators", label: t("H&S training delivery"), type: getIndicatorType(activityData?.hsTrainingConfirmation) },
|
|
100
|
+
{ icon: "Users", label: t("Workers safe pairing"), type: getIndicatorType(activityData?.duosFormed) },
|
|
101
|
+
{
|
|
102
|
+
icon: "Bear",
|
|
103
|
+
label: t("No children"),
|
|
104
|
+
type: getIndicatorType(activityData?.presenceOfChildren),
|
|
105
|
+
},
|
|
106
|
+
{ icon: "Security", label: t("Security presence"), type: getIndicatorType(activityData?.focalPointPresent) },
|
|
107
|
+
{ icon: "UserCircle", label: t("Relay presence"), type: getIndicatorType(activityData?.relayPresent) },
|
|
108
|
+
];
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const getMapDataFromActivity = (activityData, t) => {
|
|
112
|
+
|
|
113
|
+
const location = activityData?.location;
|
|
114
|
+
const perimeter = location?.perimeter;
|
|
115
|
+
|
|
116
|
+
const area = Array.isArray(perimeter) && perimeter.length > 0
|
|
117
|
+
? perimeter
|
|
118
|
+
.filter((coord) => Array.isArray(coord) && coord.length >= 2)
|
|
119
|
+
.map((coord) => {
|
|
120
|
+
const first = typeof coord[0] === 'number' ? coord[0] : parseFloat(coord[0]);
|
|
121
|
+
const second = typeof coord[1] === 'number' ? coord[1] : parseFloat(coord[1]);
|
|
122
|
+
|
|
123
|
+
if (isNaN(first) || isNaN(second) || !isFinite(first) || !isFinite(second)) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Try both formats and use Leaflet to validate
|
|
128
|
+
// First try as [lat, lng]
|
|
129
|
+
try {
|
|
130
|
+
const latLng1 = L.latLng(first, second);
|
|
131
|
+
if (latLng1.lat >= -90 && latLng1.lat <= 90 &&
|
|
132
|
+
latLng1.lng >= -180 && latLng1.lng <= 180) {
|
|
133
|
+
return [latLng1.lat, latLng1.lng];
|
|
134
|
+
}
|
|
135
|
+
} catch (e) {
|
|
136
|
+
// Not valid as [lat, lng], try swapping
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Try as [lng, lat] (GeoJSON format) - swap them
|
|
140
|
+
try {
|
|
141
|
+
const latLng2 = L.latLng(second, first);
|
|
142
|
+
if (latLng2.lat >= -90 && latLng2.lat <= 90 &&
|
|
143
|
+
latLng2.lng >= -180 && latLng2.lng <= 180) {
|
|
144
|
+
return [latLng2.lat, latLng2.lng];
|
|
145
|
+
}
|
|
146
|
+
} catch (e) {
|
|
147
|
+
// Invalid coordinates
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return null;
|
|
151
|
+
})
|
|
152
|
+
.filter((coord) => coord !== null)
|
|
153
|
+
: null;
|
|
154
|
+
|
|
155
|
+
// Don't return early - we need to check for markers even if there's no perimeter
|
|
156
|
+
|
|
157
|
+
const mapData = [];
|
|
158
|
+
const baseColor = '#15FFFFB2';
|
|
159
|
+
const locationName = location?.name || activityData?.name || t("Activity Location");
|
|
160
|
+
const datastakeId = location?.datastakeId || activityData?.datastakeId;
|
|
161
|
+
|
|
162
|
+
// Helper to validate coordinates
|
|
163
|
+
const isValidCoordinate = (coord) => {
|
|
164
|
+
const num = typeof coord === 'number' ? coord : parseFloat(coord);
|
|
165
|
+
return !isNaN(num) && isFinite(num);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// Entry 1: Perimeter polygon (independent - show if it exists)
|
|
169
|
+
if (area && area.length >= 3) {
|
|
170
|
+
mapData.push({
|
|
171
|
+
_id: {},
|
|
172
|
+
id: `${activityData?.id || activityData?.datastakeId || 'perimeter'}-perimeter`,
|
|
173
|
+
area: area,
|
|
174
|
+
color: baseColor,
|
|
175
|
+
// No gps property - this will display only the polygon without markers
|
|
176
|
+
name: locationName,
|
|
177
|
+
plotName: locationName,
|
|
178
|
+
territoryTitle: t("Associated Plot"),
|
|
179
|
+
subTitle: t("Activity Location"),
|
|
180
|
+
type: t("Activity Location"),
|
|
181
|
+
datastakeId: datastakeId,
|
|
182
|
+
sources: null,
|
|
183
|
+
link: null,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Entry 2: Arrival marker (independent - show if it exists)
|
|
188
|
+
const arrivalLat = activityData?.locationCheckArrival?.latitude;
|
|
189
|
+
const arrivalLng = activityData?.locationCheckArrival?.longitude;
|
|
190
|
+
if (isValidCoordinate(arrivalLat) && isValidCoordinate(arrivalLng)) {
|
|
191
|
+
mapData.push({
|
|
192
|
+
_id: {},
|
|
193
|
+
id: `${activityData?.id || activityData?.datastakeId || 'arrival'}-arrival`,
|
|
194
|
+
// Include area if it exists, so marker can show on top of polygon
|
|
195
|
+
area: area && area.length >= 3 ? area : null,
|
|
196
|
+
color: baseColor,
|
|
197
|
+
gps: {
|
|
198
|
+
latitude: typeof arrivalLat === 'number' ? arrivalLat : parseFloat(arrivalLat),
|
|
199
|
+
longitude: typeof arrivalLng === 'number' ? arrivalLng : parseFloat(arrivalLng),
|
|
200
|
+
},
|
|
201
|
+
name: t("Activity Start"),
|
|
202
|
+
plotName: locationName,
|
|
203
|
+
territoryTitle: t("Associated Plot"),
|
|
204
|
+
datastakeId: `${datastakeId}-arrival`,
|
|
205
|
+
sources: null,
|
|
206
|
+
link: null,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Entry 3: Departure marker (independent - show if it exists)
|
|
211
|
+
const departureLat = activityData?.locationCheckDeparture?.latitude;
|
|
212
|
+
const departureLng = activityData?.locationCheckDeparture?.longitude;
|
|
213
|
+
if (isValidCoordinate(departureLat) && isValidCoordinate(departureLng)) {
|
|
214
|
+
mapData.push({
|
|
215
|
+
_id: {},
|
|
216
|
+
id: `${activityData?.id || activityData?.datastakeId || 'departure'}-departure`,
|
|
217
|
+
// Include area if it exists, so marker can show on top of polygon
|
|
218
|
+
area: area && area.length >= 3 ? area : null,
|
|
219
|
+
color: baseColor,
|
|
220
|
+
gps: {
|
|
221
|
+
latitude: typeof departureLat === 'number' ? departureLat : parseFloat(departureLat),
|
|
222
|
+
longitude: typeof departureLng === 'number' ? departureLng : parseFloat(departureLng),
|
|
223
|
+
},
|
|
224
|
+
name: t("Activity End"),
|
|
225
|
+
plotName: locationName,
|
|
226
|
+
territoryTitle: t("Associated Plot"),
|
|
227
|
+
datastakeId: `${datastakeId}-departure`,
|
|
228
|
+
markerColor: "#FF7A45",
|
|
229
|
+
sources: null,
|
|
230
|
+
link: null,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Return mapData even if empty - let the map component handle empty arrays
|
|
235
|
+
return mapData;
|
|
236
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { DashboardLayout, Header, KeyIndicators } from '../../../../../../src/index.js'
|
|
3
|
+
import { getKeyIndicatorsRowConfig, getSiteConditionsConfig } from './config';
|
|
4
|
+
import { getActivityIndicatorsConfig } from './helper';
|
|
5
|
+
import ActivityLocation from './components/ActivityLocation/index.jsx';
|
|
6
|
+
import ActivityImagery from './components/ActivityImagery/index.jsx';
|
|
7
|
+
import WorkersDistribution from './components/WorkersDistribution/index.jsx';
|
|
8
|
+
import MangroveGrowthAndSurvival from './components/MangroveGrowthAndSurvival/index.jsx';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const MonitoringActivitySummary = ({ activityData, supportText, onDownload, downloadDisabled, actionButtons, breadcrumbs, goBackTo, loading, t = () => { } }) => {
|
|
12
|
+
const keyIndicatorsConfig = useMemo(() => getKeyIndicatorsRowConfig({ t, data: activityData }), [t, activityData]);
|
|
13
|
+
|
|
14
|
+
// Activity Indicators Config - mapped from activityData
|
|
15
|
+
const activityIndicatorsConfig = useMemo(() =>
|
|
16
|
+
getActivityIndicatorsConfig(activityData, t),
|
|
17
|
+
[activityData, t]
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
// Site Conditions Config
|
|
21
|
+
const siteConditionsConfig = useMemo(() =>
|
|
22
|
+
getSiteConditionsConfig({ t, data: activityData }),
|
|
23
|
+
[t, activityData]
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
console.log({activityData});
|
|
28
|
+
return (
|
|
29
|
+
<DashboardLayout
|
|
30
|
+
header={
|
|
31
|
+
<Header
|
|
32
|
+
title={'Monitoring Activity Summary'}
|
|
33
|
+
supportText={supportText || ''}
|
|
34
|
+
onDownload={onDownload}
|
|
35
|
+
downloadDisabled={downloadDisabled}
|
|
36
|
+
actionButtons={actionButtons}
|
|
37
|
+
breadcrumbs={breadcrumbs}
|
|
38
|
+
goBackTo={goBackTo}
|
|
39
|
+
loading={loading}
|
|
40
|
+
/>
|
|
41
|
+
}
|
|
42
|
+
>
|
|
43
|
+
<section>
|
|
44
|
+
<KeyIndicators title={t("Key Information")} config={keyIndicatorsConfig} loading={loading} />
|
|
45
|
+
</section>
|
|
46
|
+
|
|
47
|
+
<ActivityLocation activityData={activityData} loading={loading} t={t} />
|
|
48
|
+
|
|
49
|
+
<KeyIndicators
|
|
50
|
+
loading={loading}
|
|
51
|
+
config={siteConditionsConfig}
|
|
52
|
+
title={t("straatos::site-conditions")}
|
|
53
|
+
widgetClassName="h-w-btn-header"
|
|
54
|
+
/>
|
|
55
|
+
|
|
56
|
+
<MangroveGrowthAndSurvival
|
|
57
|
+
activityData={activityData}
|
|
58
|
+
loading={loading}
|
|
59
|
+
t={t}
|
|
60
|
+
/>
|
|
61
|
+
|
|
62
|
+
</DashboardLayout>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default MonitoringActivitySummary;
|
package/src/pages.js
CHANGED
|
@@ -21,6 +21,7 @@ export { default as RestorationActivitySummary } from './@daf/pages/Summary/Acti
|
|
|
21
21
|
export { default as PlantingCycleSummary } from './@daf/pages/Summary/Activities/PlantingCycle/index.jsx';
|
|
22
22
|
export { default as MonitoringCampaignSummary } from './@daf/pages/Summary/Activities/MonitoringCampaign/index.jsx';
|
|
23
23
|
export { default as MineSummary } from './@daf/pages/Summary/Minesite/index.jsx';
|
|
24
|
+
export { default as MonitoringActivitySummary } from './@daf/pages/Summary/Activities/Monitoring/index.jsx';
|
|
24
25
|
|
|
25
26
|
// View
|
|
26
27
|
export { default as View } from './@daf/pages/View/index.jsx';
|