datastake-daf 0.6.763 → 0.6.765

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 (42) hide show
  1. package/dist/components/index.js +45 -19
  2. package/dist/hooks/index.js +1 -1
  3. package/dist/layouts/index.js +36 -11
  4. package/dist/pages/index.js +1974 -999
  5. package/dist/services/index.js +1 -21
  6. package/dist/style/datastake/mapbox-gl.css +330 -0
  7. package/package.json +1 -1
  8. package/src/@daf/core/components/Screens/TableScreen/TablePageWithTabs/index.jsx +9 -4
  9. package/src/@daf/core/components/Table/index.jsx +2 -2
  10. package/src/@daf/core/components/UI/KeyIndicatorNavigateLabel/index.jsx +29 -0
  11. package/src/@daf/hooks/useFilters.js +1 -1
  12. package/src/@daf/layouts/AppLayout/components/MobileDrawer/index.js +4 -2
  13. package/src/@daf/layouts/AppLayout/components/UserDropdown/index.js +23 -2
  14. package/src/@daf/layouts/AppLayout/index.jsx +2 -0
  15. package/src/@daf/pages/Dashboards/SupplyChain/components/ChartsContainer/components/GenderDistribution/config.js +7 -6
  16. package/src/@daf/pages/Dashboards/SupplyChain/components/ChartsContainer/components/GenderDistribution/index.js +2 -1
  17. package/src/@daf/pages/Dashboards/SupplyChain/components/KeyIndicators/config.js +29 -53
  18. package/src/@daf/pages/Dashboards/SupplyChain/index.jsx +2 -2
  19. package/src/@daf/pages/Dashboards/UserDashboard/components/KeyIndicators/config.js +36 -70
  20. package/src/@daf/pages/Documents/index.jsx +9 -13
  21. package/src/@daf/pages/Events/Activities/index.jsx +8 -21
  22. package/src/@daf/pages/Events/Incidents/index.jsx +8 -21
  23. package/src/@daf/pages/Events/index.jsx +8 -20
  24. package/src/@daf/pages/Locations/MineSite/index.jsx +8 -21
  25. package/src/@daf/pages/Locations/index.jsx +9 -12
  26. package/src/@daf/pages/Partners/columns.js +421 -0
  27. package/src/@daf/pages/Partners/config.js +32 -0
  28. package/src/@daf/pages/Partners/create.jsx +145 -0
  29. package/src/@daf/pages/Partners/edit.jsx +98 -0
  30. package/src/@daf/pages/Partners/hook.js +153 -0
  31. package/src/@daf/pages/Partners/index.jsx +233 -8
  32. package/src/@daf/pages/Stakeholders/Operators/index.jsx +8 -22
  33. package/src/@daf/pages/Stakeholders/Workers/index.jsx +8 -21
  34. package/src/@daf/pages/Stakeholders/index.jsx +9 -10
  35. package/src/@daf/pages/Summary/Activities/PlantingCycle/config.js +40 -0
  36. package/src/@daf/pages/Summary/Activities/PlantingCycle/helper.js +122 -0
  37. package/src/@daf/pages/Summary/Activities/PlantingCycle/index.jsx +46 -0
  38. package/src/@daf/pages/Summary/Activities/Restoration/config.js +1 -1
  39. package/src/@daf/pages/hook.js +34 -0
  40. package/src/@daf/services/PartnerService.js +1 -16
  41. package/src/index.js +1 -1
  42. package/src/pages.js +2 -0
@@ -0,0 +1,153 @@
1
+ import { useCallback, useState } from "react";
2
+ import PartnerService from "../../services/PartnerService.js";
3
+ import { message } from "antd";
4
+
5
+ export const usePartnersActions = ({
6
+ activeTab,
7
+ filters,
8
+ setTotalRequests,
9
+ t = () => {},
10
+ }) => {
11
+ const [loading, setLoading] = useState(false);
12
+ const [data, setData] = useState([]);
13
+ const [requestDataFetch, setRequestDataFetch] = useState(false);
14
+ const [initFetchDone, setInitFetchDone] = useState(false);
15
+
16
+ const fetchData = useCallback(async () => {
17
+ setLoading(true);
18
+ setData([]);
19
+
20
+ try {
21
+ const { data } = await PartnerService.get(activeTab, {
22
+ ...filters,
23
+ type: activeTab,
24
+ });
25
+ setData(data);
26
+ setTotalRequests(data?.meta?.total || 0);
27
+ } catch (err) {
28
+ console.log(err);
29
+ }
30
+
31
+ setLoading(false);
32
+
33
+ if (!initFetchDone) {
34
+ setInitFetchDone(true);
35
+ }
36
+ }, [activeTab, filters, initFetchDone, setLoading, setTotalRequests, setData, setInitFetchDone]);
37
+
38
+ const accept = useCallback(
39
+ async (id) => {
40
+ setLoading(true);
41
+
42
+ try {
43
+ await PartnerService.accept(id);
44
+ setRequestDataFetch(true);
45
+ } catch (err) {
46
+ console.log(err);
47
+ }
48
+
49
+ setLoading(false);
50
+ },
51
+ [setLoading],
52
+ );
53
+
54
+ const decline = useCallback(
55
+ async (id) => {
56
+ setLoading(true);
57
+
58
+ try {
59
+ await PartnerService.decline(id);
60
+ setRequestDataFetch(true);
61
+ } catch (err) {
62
+ console.log(err);
63
+ }
64
+
65
+ setLoading(false);
66
+ },
67
+ [setLoading],
68
+ );
69
+
70
+ const activate = useCallback(
71
+ async (id) => {
72
+ setLoading(true);
73
+
74
+ try {
75
+ await PartnerService.activate(id);
76
+ setRequestDataFetch(true);
77
+ } catch (err) {
78
+ console.log(err);
79
+ }
80
+
81
+ setLoading(false);
82
+ },
83
+ [setLoading],
84
+ );
85
+
86
+ const suspend = useCallback(
87
+ async (id) => {
88
+ setLoading(true);
89
+
90
+ try {
91
+ await PartnerService.suspend(id);
92
+ message.success(t("Partner suspended successfully"));
93
+ setRequestDataFetch(true);
94
+ } catch (err) {
95
+ console.log(err);
96
+ }
97
+
98
+ setLoading(false);
99
+ },
100
+ [setLoading],
101
+ );
102
+
103
+ const resendInvite = useCallback(
104
+ async (id) => {
105
+ setLoading(true);
106
+
107
+ try {
108
+ await PartnerService.resendInvite(id);
109
+ await fetchData();
110
+ } catch (err) {
111
+ console.log(err);
112
+ }
113
+
114
+ setLoading(false);
115
+ },
116
+ [fetchData, setLoading],
117
+ );
118
+
119
+ const block = useCallback(
120
+ async (id) => {
121
+ setLoading(true);
122
+
123
+ try {
124
+ await PartnerService.block(id);
125
+ await fetchData();
126
+ } catch (err) {
127
+ console.log(err);
128
+ }
129
+
130
+ setLoading(false);
131
+ },
132
+ [fetchData, setLoading],
133
+ );
134
+
135
+ return {
136
+ loading,
137
+ setLoading,
138
+ data,
139
+ setData,
140
+ requestDataFetch,
141
+ setRequestDataFetch,
142
+ fetchData,
143
+ accept,
144
+ decline,
145
+ activate,
146
+ suspend,
147
+ resendInvite,
148
+ block,
149
+ setTotalRequests,
150
+ initFetchDone,
151
+ setInitFetchDone,
152
+ }
153
+ }
@@ -1,11 +1,236 @@
1
- import React from 'react';
1
+ import React, { useState, useMemo, useEffect, useCallback } from 'react'
2
+ import { Modal } from 'antd';
3
+ import { usePartnersActions } from './hook.js';
4
+ import { getColumns } from './columns.js';
5
+ import TablePageWithTabs from '../../core/components/Screens/TableScreen/TablePageWithTabs/index.jsx';
6
+ import { checkboxConfig, selectFiltersConfig, filtersConfig } from './config.js';
7
+ import Create from './create.jsx';
8
+ import { useGetQueryParams } from '../../hooks/useGetQueryParams.js';
9
+ import ModalHeader from '../../core/components/Header/ModalHeader/index.jsx';
10
+ import Edit from './edit.jsx';
2
11
 
3
- const Partners = () => {
12
+ const PartnersTable = ({
13
+ t = () => {},
14
+ goTo = () => {},
15
+ user = {},
16
+ options = {},
17
+ getRedirectLink = () => {},
18
+ theme = {},
19
+ isMobile,
20
+ APP,
21
+ location,
22
+ breadcrumbs = [],
23
+ getApiBaseUrl = () => {},
24
+ getAppHeader = () => {},
25
+ query = {},
26
+ ajaxForms = {},
27
+ changeAjaxForms = () => {},
28
+ ajaxOptions = {},
29
+ changeAjaxOptions = () => {},
30
+ applications = [],
31
+ extendingFilters = {},
32
+ searchLocationParams,
33
+ }) => {
34
+ const [totalRequests, setTotalRequests] = useState(0);
35
+ const [selectOptions, setSelectOptions] = useState();
36
+ const [activeTab, setActiveTab] = useState();
37
+ const [pendingEditId, setPendingEditId] = useState(null);
38
+ const [openModal, setOpenModal] = useState(false);
39
+
40
+ const tabs = useMemo(() => [
41
+ { label: t("List"), key: "partners" },
42
+ { label: (
43
+ <div className="tab-cont">
44
+ <div className="flex flex-column justify-content-center">
45
+ {t('Requests')}
46
+ </div>
47
+ {typeof totalRequests === 'number' ? (
48
+ <div className="flex flex-column justify-content-center">
49
+ <div className="bubble">
50
+ <div className="flex flex-column justify-content-center">
51
+ {totalRequests}
52
+ </div>
53
+ </div>
54
+ </div>
55
+ ) : null}
56
+ </div>
57
+ ),
58
+ key: "requests"
59
+ },
60
+ ], [t, totalRequests]);
61
+
62
+ const { paginationQuery, searchParams, otherParams, sortBy, sortDir, } = useGetQueryParams({location});
63
+
64
+ const filters = useMemo(() => {
65
+ const cleanSearchParams = Object.fromEntries(
66
+ Object.entries(searchParams).filter(([_, value]) => value != null && value !== '')
67
+ );
68
+
69
+ return {
70
+ pagination: paginationQuery,
71
+ ...(Object.keys(otherParams).length > 0 && otherParams ),
72
+ ...(Object.keys(cleanSearchParams).length > 0 && { search: cleanSearchParams }),
73
+ tab: activeTab,
74
+ sortBy: {
75
+ [sortBy || "updatedAt"]: sortDir ? (sortDir === "ascend" ? 1 : -1) : -1,
76
+ },
77
+ ...extendingFilters,
78
+ }
79
+ }, [location.search, activeTab, JSON.stringify(extendingFilters)]);
80
+
81
+ const {
82
+ loading,
83
+ data,
84
+ requestDataFetch,
85
+ fetchData,
86
+ accept,
87
+ decline,
88
+ activate,
89
+ suspend,
90
+ resendInvite,
91
+ block,
92
+ setRequestDataFetch,
93
+ initFetchDone,
94
+ setInitFetchDone,
95
+ } = usePartnersActions({
96
+ activeTab,
97
+ filters,
98
+ setTotalRequests,
99
+ t,
100
+ });
101
+
102
+ const columns = useMemo(() => getColumns({
103
+ t,
104
+ accept,
105
+ decline,
106
+ suspend,
107
+ resendInvite,
108
+ activate,
109
+ activeTab,
110
+ selectOptions,
111
+ block,
112
+ setOpen: setOpenModal,
113
+ }), [t, accept, decline, suspend, resendInvite, activate, activeTab, selectOptions, block, setOpenModal]);
114
+
115
+ const handleActiveTabChange = useCallback((value) => {
116
+ setActiveTab(value);
117
+ }, []);
118
+
119
+ useEffect(() => {
120
+ if (requestDataFetch) {
121
+ fetchData();
122
+ setRequestDataFetch(false);
123
+ }
124
+ }, [requestDataFetch, fetchData, setRequestDataFetch]);
125
+
126
+ useEffect(() => {
127
+ if (searchLocationParams.has("datastakeId") && !pendingEditId) {
128
+ setPendingEditId(searchLocationParams.get("datastakeId"));
129
+ }
130
+ }, [searchLocationParams, pendingEditId]);
131
+
132
+ useEffect(() => {
133
+ if (pendingEditId) {
134
+ const partner = data.find((partner) => partner.datastakeId === pendingEditId);
135
+ if (partner) {
136
+ setOpenModal(partner);
137
+ setPendingEditId(null);
138
+ }
139
+ }
140
+ }, [data, pendingEditId]);
141
+
142
+ useEffect(() => {
143
+ fetchData();
144
+ }, [filters]);
145
+
146
+ const headerTooltip = useMemo(() => ({
147
+ title: t("Partners"),
148
+ content: (
149
+ <div className="max-w-250">
150
+ {t(
151
+ "Partners are organisations sharing their information with you, or receiving your information.",
152
+ )}
153
+ <br />
154
+ {t("Only authorised users can create partnerships.")}
155
+ </div>
156
+ ),
157
+ }), [t]);
158
+
159
+ const drawerTooltip = useMemo(() => ({
160
+ content: (
161
+ <div className="max-w-250">
162
+ {t("Source: This partner will share information with you.")},
163
+ <br />
164
+ {t("Client: You will share information with this partner.")},
165
+ <br />
166
+ {t("Exchange: You will both share information with each other.")}
167
+ </div>
168
+ ),
169
+ }), [t]);
170
+
4
171
  return (
5
- <div>
6
- <h1>Partners</h1>
7
- </div>
8
- );
9
- };
172
+ <>
173
+ <TablePageWithTabs
174
+ t={t}
175
+ tabs={tabs}
176
+ title={t("Partners")}
177
+ breadCrumbs={breadcrumbs}
178
+ location={location}
179
+ loading={loading}
180
+ goTo={goTo}
181
+ defaultActiveTab={"partners"}
182
+ columns={columns}
183
+ data={data}
184
+ checkboxConfig={checkboxConfig}
185
+ APP={APP}
186
+ getApiBaseUrl={getApiBaseUrl}
187
+ selectOptions={selectOptions}
188
+ selectFiltersConfig={selectFiltersConfig}
189
+ getRedirectLink={getRedirectLink}
190
+ filtersConfig={filtersConfig}
191
+ isMobile={isMobile}
192
+ view="partners"
193
+ getActiveTab={handleActiveTabChange}
194
+ headerTooltip={headerTooltip}
195
+ drawerTitle={t("Create Partner")}
196
+ drawerTooltip={drawerTooltip}
197
+ >
198
+ {({onDrawerClose}) => (
199
+ <Create
200
+ query={query}
201
+ goTo={goTo}
202
+ user={user}
203
+ t={t}
204
+ ajaxForms={ajaxForms}
205
+ changeAjaxForms={changeAjaxForms}
206
+ ajaxOptions={ajaxOptions}
207
+ changeAjaxOptions={changeAjaxOptions}
208
+ onClose={onDrawerClose}
209
+ fetchData={() => setRequestDataFetch(true)}
210
+ APP={APP}
211
+ getAppHeader={getAppHeader}
212
+ getApiBaseUrl={getApiBaseUrl}
213
+ />
214
+ )}
215
+ </TablePageWithTabs>
216
+ <Modal
217
+ open={!!openModal}
218
+ onCancel={() => setOpenModal(false)}
219
+ footer={null}
220
+ title={<ModalHeader title={t("Edit Settings")} />}
221
+ >
222
+ {openModal ? (
223
+ <Edit
224
+ partner={openModal}
225
+ fetchData={() => setRequestDataFetch(true)}
226
+ onClose={() => setOpenModal(false)}
227
+ t={t}
228
+ />
229
+ ) : null}
230
+ </Modal>
231
+ <input id="myInput" hidden />
232
+ </>
233
+ )
234
+ }
10
235
 
11
- export default Partners
236
+ export default PartnersTable
@@ -5,6 +5,7 @@ import { checkboxConfig, getFiltersConfig, filtersConfig, getFilterOptions } fro
5
5
  import { useGetQueryParams } from '../../../hooks/useGetQueryParams.js';
6
6
  import StakeholdersCreate from './create.jsx';
7
7
  import { displayMessage } from '../../../../helpers/messages.js';
8
+ import { useFetchData } from '../../hook.js';
8
9
 
9
10
  const OperatorsTable = ({
10
11
  t = () => {},
@@ -53,28 +54,13 @@ const OperatorsTable = ({
53
54
  applications,
54
55
  }), [t, goTo, user, options, activeTab, getRedirectLink, theme, data, applications]);
55
56
 
56
-
57
- const { paginationQuery, searchParams, otherParams, sortBy, sortDir } = useGetQueryParams({location});
58
-
59
- const filters = useMemo(() => {
60
- return {
61
- ...otherParams,
62
- ...extendingFilters
63
- }
64
- }, [otherParams, extendingFilters])
65
-
66
- useEffect(() => {
67
- getData({
68
- pagination: paginationQuery,
69
- ...(Object.keys(searchParams).length > 0 && { search: searchParams }),
70
- ...otherParams,
71
- tab: activeTab,
72
- sortBy: {
73
- [sortBy || "updatedAt"]: sortDir ? (sortDir === "ascend" ? 1 : -1) : -1,
74
- },
75
- ...extendingFilters
76
- }, 'operators')
77
- }, [location.search, activeTab, JSON.stringify(extendingFilters)]);
57
+ useFetchData({
58
+ location,
59
+ getData,
60
+ activeTab,
61
+ extendingFilters,
62
+ subject: 'operators',
63
+ })
78
64
 
79
65
  const selectFiltersConfig = useMemo(() => {
80
66
  return getFiltersConfig({t});
@@ -5,6 +5,7 @@ import { checkboxConfig, getFiltersConfig, filtersConfig, getFilterOptions } fro
5
5
  import { useGetQueryParams } from '../../../hooks/useGetQueryParams.js';
6
6
  import StakeholdersCreate from './create.jsx';
7
7
  import { displayMessage } from '../../../../helpers/messages.js';
8
+ import { useFetchData } from '../../hook.js';
8
9
 
9
10
  const WorkersTable = ({
10
11
  t = () => {},
@@ -53,27 +54,13 @@ const WorkersTable = ({
53
54
  applications,
54
55
  }), [t, goTo, user, options, activeTab, getRedirectLink, theme, data, applications]);
55
56
 
56
- const { paginationQuery, searchParams, otherParams, sortBy, sortDir } = useGetQueryParams({location});
57
-
58
- const filters = useMemo(() => {
59
- return {
60
- ...otherParams,
61
- ...extendingFilters
62
- }
63
- }, [otherParams, extendingFilters])
64
-
65
- useEffect(() => {
66
- getData({
67
- pagination: paginationQuery,
68
- ...(Object.keys(searchParams).length > 0 && { search: searchParams }),
69
- ...otherParams,
70
- tab: activeTab,
71
- sortBy: {
72
- [sortBy || "updatedAt"]: sortDir ? (sortDir === "ascend" ? 1 : -1) : -1,
73
- },
74
- ...extendingFilters
75
- }, 'workers')
76
- }, [location.search, activeTab, JSON.stringify(extendingFilters)]);
57
+ useFetchData({
58
+ location,
59
+ getData,
60
+ activeTab,
61
+ extendingFilters,
62
+ subject: 'workers',
63
+ })
77
64
 
78
65
  const selectFiltersConfig = useMemo(() => {
79
66
  return getFiltersConfig({t});
@@ -6,6 +6,7 @@ import { useGetQueryParams } from '../../hooks/useGetQueryParams.js';
6
6
  import StakeholdersCreate from './create.jsx';
7
7
  import { displayMessage } from '../../../helpers/messages.js';
8
8
  import DAFHeader from '../../core/components/Header/index.jsx';
9
+ import { useFetchData } from '../hook.js';
9
10
 
10
11
  const StakeholdersTable = ({
11
12
  t = () => {},
@@ -36,6 +37,7 @@ const StakeholdersTable = ({
36
37
  applications = [],
37
38
  subjectClear = () => {},
38
39
  breadcrumbs = [],
40
+ extendingFilters = {},
39
41
  }) => {
40
42
  const [selectOptions, setSelectOptions] = useState();
41
43
  const [activeTab, setActiveTab] = useState("own");
@@ -51,16 +53,13 @@ const StakeholdersTable = ({
51
53
  applications,
52
54
  }), [t, goTo, user, options, activeTab, getRedirectLink, theme, applications]);
53
55
 
54
- const { paginationQuery, searchParams, otherParams } = useGetQueryParams({location});
55
-
56
- useEffect(() => {
57
- getData({
58
- pagination: paginationQuery,
59
- ...(Object.keys(otherParams).length > 0 && { filters: otherParams }),
60
- ...(Object.keys(searchParams).length > 0 && { search: searchParams }),
61
- tab: activeTab,
62
- }, 'stakeholders')
63
- }, [paginationQuery, otherParams, searchParams, activeTab]);
56
+ useFetchData({
57
+ location,
58
+ getData,
59
+ activeTab,
60
+ extendingFilters,
61
+ subject: 'stakeholders',
62
+ })
64
63
 
65
64
  const selectFiltersConfig = useMemo(() => {
66
65
  return getFiltersConfig({t});
@@ -0,0 +1,40 @@
1
+ import React from "react";
2
+
3
+ export const getKeyIndicatorsRowConfig = ({ t, data = {} }) => [
4
+ {
5
+ label: t('Region'),
6
+ render: () => {
7
+ // const region = data?.region;
8
+ return <div>{ '-'}</div>;
9
+ }
10
+ },
11
+ {
12
+ label: t('Associated Plots'),
13
+ render: () => {
14
+ // const plotsCount = data?.associatedPlotsCount || '0';
15
+ return <div>{'0'}</div>
16
+ }
17
+ },
18
+ {
19
+ label: t('Implementation Partners'),
20
+ render: () => {
21
+ // const partnersCount = data?.partnersCount || '0';
22
+ return <div>{'0'}</div>
23
+ }
24
+ },
25
+ {
26
+ label: t('Total Activities'),
27
+ render: () => {
28
+ // const activitiesCount = data?.activitiesCount || '0';
29
+ return <div>{'0'}</div>
30
+ }
31
+ },
32
+ {
33
+ label: t('Information Sources'),
34
+ render: () => {
35
+ // const sourcesCount = data?.informationSourcesCount || '0';
36
+ return <div>{'0'}</div>
37
+ }
38
+ },
39
+ ];
40
+
@@ -0,0 +1,122 @@
1
+ import L from "leaflet";
2
+
3
+ export const getMapDataFromActivity = (activityData, t) => {
4
+ const location = activityData?.location;
5
+ const perimeter = location?.perimeter;
6
+
7
+ const area = Array.isArray(perimeter) && perimeter.length > 0
8
+ ? perimeter
9
+ .filter((coord) => Array.isArray(coord) && coord.length >= 2)
10
+ .map((coord) => {
11
+ const first = typeof coord[0] === 'number' ? coord[0] : parseFloat(coord[0]);
12
+ const second = typeof coord[1] === 'number' ? coord[1] : parseFloat(coord[1]);
13
+
14
+ if (isNaN(first) || isNaN(second) || !isFinite(first) || !isFinite(second)) {
15
+ return null;
16
+ }
17
+
18
+ // Try both formats and use Leaflet to validate
19
+ // First try as [lat, lng]
20
+ try {
21
+ const latLng1 = L.latLng(first, second);
22
+ if (latLng1.lat >= -90 && latLng1.lat <= 90 &&
23
+ latLng1.lng >= -180 && latLng1.lng <= 180) {
24
+ return [latLng1.lat, latLng1.lng];
25
+ }
26
+ } catch (e) {
27
+ // Not valid as [lat, lng], try swapping
28
+ }
29
+
30
+ // Try as [lng, lat] (GeoJSON format) - swap them
31
+ try {
32
+ const latLng2 = L.latLng(second, first);
33
+ if (latLng2.lat >= -90 && latLng2.lat <= 90 &&
34
+ latLng2.lng >= -180 && latLng2.lng <= 180) {
35
+ return [latLng2.lat, latLng2.lng];
36
+ }
37
+ } catch (e) {
38
+ // Invalid coordinates
39
+ }
40
+
41
+ return null;
42
+ })
43
+ .filter((coord) => coord !== null)
44
+ : null;
45
+
46
+ const mapData = [];
47
+ const baseColor = '#15FFFFB2';
48
+ const locationName = location?.name || activityData?.name || t("Activity Location");
49
+ const datastakeId = location?.datastakeId || activityData?.datastakeId;
50
+
51
+ // Helper to validate coordinates
52
+ const isValidCoordinate = (coord) => {
53
+ const num = typeof coord === 'number' ? coord : parseFloat(coord);
54
+ return !isNaN(num) && isFinite(num);
55
+ };
56
+
57
+ // Entry 1: Perimeter polygon (independent - show if it exists)
58
+ if (area && area.length >= 3) {
59
+ mapData.push({
60
+ _id: {},
61
+ id: `${activityData?.id || activityData?.datastakeId || 'perimeter'}-perimeter`,
62
+ area: area,
63
+ color: baseColor,
64
+ name: locationName,
65
+ plotName: locationName,
66
+ territoryTitle: t("Associated Plot"),
67
+ subTitle: t("Activity Location"),
68
+ type: t("Activity Location"),
69
+ datastakeId: datastakeId,
70
+ sources: null,
71
+ link: null,
72
+ });
73
+ }
74
+
75
+ // Entry 2: Arrival marker (independent - show if it exists)
76
+ const arrivalLat = activityData?.locationCheckArrival?.latitude;
77
+ const arrivalLng = activityData?.locationCheckArrival?.longitude;
78
+ if (isValidCoordinate(arrivalLat) && isValidCoordinate(arrivalLng)) {
79
+ mapData.push({
80
+ _id: {},
81
+ id: `${activityData?.id || activityData?.datastakeId || 'arrival'}-arrival`,
82
+ area: area && area.length >= 3 ? area : null,
83
+ color: baseColor,
84
+ gps: {
85
+ latitude: typeof arrivalLat === 'number' ? arrivalLat : parseFloat(arrivalLat),
86
+ longitude: typeof arrivalLng === 'number' ? arrivalLng : parseFloat(arrivalLng),
87
+ },
88
+ name: t("Activity Start"),
89
+ plotName: locationName,
90
+ territoryTitle: t("Associated Plot"),
91
+ datastakeId: `${datastakeId}-arrival`,
92
+ sources: null,
93
+ link: null,
94
+ });
95
+ }
96
+
97
+ // Entry 3: Departure marker (independent - show if it exists)
98
+ const departureLat = activityData?.locationCheckDeparture?.latitude;
99
+ const departureLng = activityData?.locationCheckDeparture?.longitude;
100
+ if (isValidCoordinate(departureLat) && isValidCoordinate(departureLng)) {
101
+ mapData.push({
102
+ _id: {},
103
+ id: `${activityData?.id || activityData?.datastakeId || 'departure'}-departure`,
104
+ area: area && area.length >= 3 ? area : null,
105
+ color: baseColor,
106
+ gps: {
107
+ latitude: typeof departureLat === 'number' ? departureLat : parseFloat(departureLat),
108
+ longitude: typeof departureLng === 'number' ? departureLng : parseFloat(departureLng),
109
+ },
110
+ name: t("Activity End"),
111
+ plotName: locationName,
112
+ territoryTitle: t("Associated Plot"),
113
+ datastakeId: `${datastakeId}-departure`,
114
+ markerColor: "#FF7A45",
115
+ sources: null,
116
+ link: null,
117
+ });
118
+ }
119
+
120
+ return mapData;
121
+ };
122
+