Skip to content

Commit 0fd4a18

Browse files
committed
reduced duplication by moving common elements into a new component
1 parent 4d777e8 commit 0fd4a18

4 files changed

Lines changed: 535 additions & 783 deletions

File tree

src/components/BMDashboard/InteractiveMap/EmbedInteractiveMap.jsx

Lines changed: 11 additions & 333 deletions
Original file line numberDiff line numberDiff line change
@@ -2,112 +2,12 @@
22
import { useState, useEffect } from 'react';
33
import { useSelector } from 'react-redux';
44
import { useHistory } from 'react-router-dom';
5-
import { MapContainer, TileLayer, CircleMarker, Popup, useMap, Tooltip } from 'react-leaflet';
5+
import { MapContainer, TileLayer } from 'react-leaflet';
66
import MarkerClusterGroup from '@changey/react-leaflet-markercluster';
77
import axios from 'axios';
88
import L from 'leaflet';
99
import { ENDPOINTS } from '../../../utils/URL';
10-
11-
/* -----------------------------------------------------
12-
THEME UPDATER FOR EMBED
13-
----------------------------------------------------- */
14-
function MapThemeUpdater({ darkMode }) {
15-
const map = useMap();
16-
useEffect(() => {
17-
map.invalidateSize();
18-
19-
const container = map.getContainer();
20-
container.classList.remove('dark-mode-map', 'light-mode-map');
21-
container.classList.add(darkMode ? 'dark-mode-map' : 'light-mode-map');
22-
23-
// Update popups and tooltips
24-
map.eachLayer(layer => {
25-
if (layer.getPopup) {
26-
const popup = layer.getPopup();
27-
if (popup) {
28-
const popupElement = popup.getElement();
29-
if (popupElement) {
30-
popupElement.classList.remove('dark-mode-popup', 'light-mode-popup');
31-
popupElement.classList.add(darkMode ? 'dark-mode-popup' : 'light-mode-popup');
32-
33-
// Hide the popup tip/arrow
34-
const tip = popupElement.querySelector('.leaflet-popup-tip');
35-
if (tip) {
36-
tip.style.display = 'none';
37-
}
38-
}
39-
}
40-
}
41-
42-
if (layer.getTooltip) {
43-
const tooltip = layer.getTooltip();
44-
if (tooltip) {
45-
const tooltipElement = tooltip.getElement();
46-
if (tooltipElement) {
47-
tooltipElement.classList.remove('dark-mode-tooltip', 'light-mode-tooltip');
48-
tooltipElement.classList.add(darkMode ? 'dark-mode-tooltip' : 'light-mode-tooltip');
49-
}
50-
}
51-
}
52-
});
53-
}, [darkMode, map]);
54-
return null;
55-
}
56-
57-
/* -----------------------------------------------------
58-
LEGEND FOR EMBED
59-
----------------------------------------------------- */
60-
function Legend() {
61-
const map = useMap();
62-
const darkMode = useSelector(state => state.theme.darkMode);
63-
64-
useEffect(() => {
65-
const legend = L.control({ position: 'bottomleft' });
66-
legend.onAdd = () => {
67-
const div = L.DomUtil.create('div', 'info legend');
68-
69-
// Enhanced styling for better visibility
70-
div.style.backgroundColor = darkMode ? 'rgba(30, 42, 58, 0.95)' : 'rgba(255, 255, 255, 0.95)';
71-
div.style.color = darkMode ? 'white' : '#222';
72-
div.style.padding = '12px 15px';
73-
div.style.borderRadius = '8px';
74-
div.style.boxShadow = darkMode ? '0 4px 15px rgba(0,0,0,0.4)' : '0 4px 15px rgba(0,0,0,0.15)';
75-
div.style.border = darkMode ? '1px solid #3a506b' : '1px solid #ccc';
76-
div.style.fontSize = '14px';
77-
div.style.fontFamily = 'Arial, sans-serif';
78-
div.style.minWidth = '160px';
79-
div.style.backdropFilter = 'blur(10px)';
80-
81-
const statuses = ['active', 'delayed', 'completed'];
82-
const colors = ['#DE6A6A', '#E3D270', '#6ACFDE'];
83-
const labels = ['Active - Red', 'Delayed - Yellow', 'Completed - Blue'];
84-
85-
div.innerHTML = `<div style="color: ${
86-
darkMode ? 'white' : '#333'
87-
}; font-weight: bold; margin-bottom: 8px; font-size: 16px;">Project Status</div>`;
88-
89-
statuses.forEach((status, i) => {
90-
div.innerHTML +=
91-
`<div style="display: flex; align-items: center; margin-bottom: 6px;">` +
92-
`<div style="width: 14px; height: 14px; border-radius: 50%; background-color: ${
93-
colors[i]
94-
}; margin-right: 10px; border: ${
95-
darkMode ? '1px solid rgba(255,255,255,0.3)' : '1px solid #333'
96-
};"></div>` +
97-
`<span style="text-transform: capitalize; color: ${
98-
darkMode ? 'white' : '#333'
99-
}; font-weight: 500;">${labels[i]}</span>` +
100-
`</div>`;
101-
});
102-
return div;
103-
};
104-
legend.addTo(map);
105-
return () => {
106-
legend.remove();
107-
};
108-
}, [map, darkMode]);
109-
return null;
110-
}
10+
import { MapThemeUpdater, Legend, ProjectMarkers, MapUtils } from './MapSharedComponents';
11111

11212
/* -----------------------------------------------------
11313
EMBED INTERACTIVE MAP
@@ -118,20 +18,6 @@ function EmbedInteractiveMap() {
11818
const [orgs, setOrgs] = useState([]);
11919
const [loading, setLoading] = useState(true);
12020

121-
// status color: active, delayed, completed
122-
const getStatusColor = status => {
123-
switch (status?.toLowerCase()) {
124-
case 'active':
125-
return '#DE6A6A';
126-
case 'delayed':
127-
return '#E3D270';
128-
case 'completed':
129-
return '#6ACFDE';
130-
default:
131-
return '#CCCCCC';
132-
}
133-
};
134-
13521
// fetch projects/orgs
13622
const fetchOrgs = async () => {
13723
try {
@@ -149,7 +35,7 @@ function EmbedInteractiveMap() {
14935
// If no data from backend, use pseudo data
15036
if (data.length === 0) {
15137
console.log('No data from backend, using pseudo data');
152-
const pseudoData = getPseudoOrgs();
38+
const pseudoData = MapUtils.getPseudoOrgs();
15339
setOrgs(pseudoData);
15440
} else {
15541
setOrgs(data);
@@ -158,51 +44,14 @@ function EmbedInteractiveMap() {
15844
} catch (error) {
15945
console.error('Error fetching project/org data:', error);
16046
// If fetch fails, use pseudo data
161-
const pseudoData = getPseudoOrgs();
47+
const pseudoData = MapUtils.getPseudoOrgs();
16248
setOrgs(pseudoData);
16349
return [];
16450
} finally {
16551
setLoading(false);
16652
}
16753
};
16854

169-
// Pseudo test data function
170-
const getPseudoOrgs = () => {
171-
return [
172-
{
173-
orgId: 9991,
174-
name: 'Test Project Alpha',
175-
status: 'active',
176-
country: 'United States',
177-
latitude: 34.1185,
178-
longitude: -118.0743,
179-
startDate: '2025-11-01',
180-
remark: 'This is a remark for Project Alpha.',
181-
},
182-
{
183-
orgId: 9992,
184-
name: 'Test Project Beta',
185-
status: 'completed',
186-
country: 'Germany',
187-
latitude: 52.52,
188-
longitude: 13.405,
189-
startDate: '2023-12-10',
190-
endDate: '2024-12-10',
191-
remark: 'Beta testing completed successfully.',
192-
},
193-
{
194-
orgId: 9993,
195-
name: 'Test Project Gamma',
196-
status: 'delayed',
197-
country: 'Japan',
198-
latitude: 35.6895,
199-
longitude: 139.6917,
200-
startDate: '2025-06-01',
201-
remark: 'Awaiting equipment shipment.',
202-
},
203-
];
204-
};
205-
20655
const handleProjectClick = org => {
20756
// Navigate to project details page
20857
history.push(`/bmdashboard/projects/${org.orgId}`);
@@ -273,191 +122,20 @@ function EmbedInteractiveMap() {
273122
maxZoom={15}
274123
/>
275124

276-
<Legend />
125+
<Legend position="bottomleft" />
277126

278127
<MarkerClusterGroup
279128
disableClusteringAtZoom={13}
280129
spiderfyOnMaxZoom={true}
281130
chunkedLoading={true}
282131
maxClusterRadius={80}
283132
>
284-
{orgs.map((org, index) => (
285-
<CircleMarker
286-
key={org.orgId || index}
287-
center={[org.latitude, org.longitude]}
288-
radius={6}
289-
pathOptions={{
290-
fillColor: getStatusColor(org.status),
291-
fillOpacity: 0.8,
292-
color: darkMode ? '#1e2a3a' : 'white',
293-
weight: 1,
294-
}}
295-
>
296-
<Tooltip
297-
className={darkMode ? 'dark-mode-tooltip' : 'light-mode-tooltip'}
298-
direction="top"
299-
offset={[0, -10]}
300-
opacity={1}
301-
permanent={false}
302-
>
303-
<div style={{ fontSize: '11px', textAlign: 'center' }}>Project #{org.orgId}</div>
304-
</Tooltip>
305-
306-
<Popup className={darkMode ? 'dark-mode-popup' : 'light-mode-popup'}>
307-
<div
308-
style={{
309-
minWidth: '200px',
310-
color: darkMode ? 'white' : '#222',
311-
fontFamily: 'Arial, sans-serif',
312-
background: darkMode ? '#2d4059' : 'white',
313-
borderRadius: '8px',
314-
boxShadow: darkMode
315-
? '0 4px 12px rgba(0, 0, 0, 0.4)'
316-
: '0 4px 12px rgba(0, 0, 0, 0.15)',
317-
border: darkMode ? '1px solid #3a506b' : '1px solid #e0e0e0',
318-
overflow: 'hidden',
319-
}}
320-
>
321-
{/* Compact Header */}
322-
<div
323-
style={{
324-
display: 'flex',
325-
alignItems: 'center',
326-
marginBottom: '10px',
327-
padding: '12px 12px 10px 12px',
328-
borderBottom: darkMode ? '1px solid #3a506b' : '1px solid #e0e0e0',
329-
background: darkMode ? 'rgba(0,0,0,0.1)' : 'rgba(0,0,0,0.02)',
330-
}}
331-
>
332-
<div
333-
style={{
334-
width: '8px',
335-
height: '8px',
336-
borderRadius: '50%',
337-
backgroundColor: getStatusColor(org.status),
338-
marginRight: '8px',
339-
flexShrink: 0,
340-
}}
341-
></div>
342-
<div style={{ flex: 1, minWidth: 0 }}>
343-
<h4
344-
style={{
345-
margin: '0 0 2px 0',
346-
fontSize: '13px',
347-
fontWeight: '600',
348-
color: darkMode ? 'white' : '#222',
349-
lineHeight: '1.3',
350-
whiteSpace: 'nowrap',
351-
overflow: 'hidden',
352-
textOverflow: 'ellipsis',
353-
}}
354-
>
355-
{org.name}
356-
</h4>
357-
<span
358-
style={{
359-
fontSize: '11px',
360-
color: darkMode ? '#b0b8c4' : '#666',
361-
textTransform: 'capitalize',
362-
}}
363-
>
364-
{org.status} • #{org.orgId}
365-
</span>
366-
</div>
367-
</div>
368-
369-
{/* Compact Details */}
370-
<div style={{ marginBottom: '12px', padding: '0 12px' }}>
371-
<div
372-
style={{
373-
display: 'flex',
374-
justifyContent: 'space-between',
375-
alignItems: 'center',
376-
marginBottom: '6px',
377-
}}
378-
>
379-
<span
380-
style={{
381-
fontSize: '11px',
382-
color: darkMode ? '#b0b8c4' : '#666',
383-
fontWeight: '500',
384-
}}
385-
>
386-
Country:
387-
</span>
388-
<span
389-
style={{
390-
fontSize: '11px',
391-
fontWeight: '600',
392-
color: darkMode ? 'white' : '#222',
393-
}}
394-
>
395-
{org.country}
396-
</span>
397-
</div>
398-
<div
399-
style={{
400-
display: 'flex',
401-
justifyContent: 'space-between',
402-
alignItems: 'center',
403-
marginBottom: '6px',
404-
}}
405-
>
406-
<span
407-
style={{
408-
fontSize: '11px',
409-
color: darkMode ? '#b0b8c4' : '#666',
410-
fontWeight: '500',
411-
}}
412-
>
413-
Start:
414-
</span>
415-
<span
416-
style={{
417-
fontSize: '11px',
418-
fontWeight: '600',
419-
color: darkMode ? 'white' : '#222',
420-
}}
421-
>
422-
{new Date(org.startDate).toLocaleDateString()}
423-
</span>
424-
</div>
425-
</div>
426-
427-
{/* Compact Button */}
428-
<div style={{ padding: '0 12px 12px 12px' }}>
429-
<button
430-
onClick={() => handleProjectClick(org)}
431-
style={{
432-
width: '100%',
433-
backgroundColor: darkMode ? '#6ACFDE' : '#4CAF50',
434-
color: 'white',
435-
padding: '8px 12px',
436-
border: 'none',
437-
borderRadius: '4px',
438-
cursor: 'pointer',
439-
fontSize: '11px',
440-
fontWeight: '600',
441-
transition: 'all 0.2s ease',
442-
textTransform: 'uppercase',
443-
letterSpacing: '0.3px',
444-
}}
445-
onMouseOver={e => {
446-
e.target.style.backgroundColor = darkMode ? '#5abfcc' : '#45a049';
447-
e.target.style.transform = 'translateY(-1px)';
448-
}}
449-
onMouseOut={e => {
450-
e.target.style.backgroundColor = darkMode ? '#6ACFDE' : '#4CAF50';
451-
e.target.style.transform = 'translateY(0)';
452-
}}
453-
>
454-
View Details
455-
</button>
456-
</div>
457-
</div>
458-
</Popup>
459-
</CircleMarker>
460-
))}
133+
<ProjectMarkers
134+
orgs={orgs}
135+
darkMode={darkMode}
136+
onProjectClick={handleProjectClick}
137+
markerRadius={6}
138+
/>
461139
</MarkerClusterGroup>
462140
</MapContainer>
463141
</div>

0 commit comments

Comments
 (0)