22import { useState , useEffect } from 'react' ;
33import { useSelector } from 'react-redux' ;
44import { useHistory } from 'react-router-dom' ;
5- import { MapContainer , TileLayer , CircleMarker , Popup , useMap , Tooltip } from 'react-leaflet' ;
5+ import { MapContainer , TileLayer } from 'react-leaflet' ;
66import MarkerClusterGroup from '@changey/react-leaflet-markercluster' ;
77import axios from 'axios' ;
88import L from 'leaflet' ;
99import { 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