Advertisement
minafaw3

MapPage

May 19th, 2025
22
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.06 KB | None | 0 0
  1. import React, {
  2. useEffect, useState, useMemo, useCallback, useRef,
  3. } from 'react';
  4. import { useDispatch, useSelector } from 'react-redux';
  5. import { useLocation } from 'react-router-dom';
  6. import { useTranslation } from 'react-i18next';
  7. import sizeMe from 'react-sizeme';
  8. import * as rootSelectors from 'ducks/selectors';
  9. import {
  10. operations as locationOperations,
  11. selectors as locationSelectors,
  12. actions as locationActions,
  13. } from 'ducks/Location';
  14. import { actions as mapActions } from 'ducks/Map';
  15. import { callTotalLocations } from 'ducks/Tool/utils';
  16. import { selectors as toolSelectors, operations as toolOperations } from 'ducks/Tool';
  17. import { operations as modalOperations } from 'ducks/Modal';
  18. import { EDIT_TOOL, LOCATION } from 'constants/modalTypes';
  19. import getMapBounds from 'utils/getMapBounds';
  20. import useAnalytics from 'shared/hooks/useAnalytics';
  21. import Map from 'shared/Map';
  22. import FilterDropdown from 'components/FilterDropdown/FilterDropdown';
  23. import {
  24. Paper, Grid, styled, useTheme,
  25. } from '@mui/material';
  26. import useGlobalSelectors from 'shared/hooks/globals/selectors';
  27. import SearchResultMarker from 'shared/SearchResultMarker/SearchResultMarker';
  28. import LocationMarker from 'shared/LocationMarker';
  29. import { useLoadingBar } from 'react-top-loading-bar';
  30. import useApig from 'shared/hooks/useApig';
  31. import { AppDispatch } from 'store/types';
  32. import { TOOL_MAP_FETCH_REQUEST } from 'ducks/types';
  33. import HideApigLoading from 'components/HideApigLoading';
  34. import { createRoot } from 'react-dom/client';
  35. import { MarkerClusterer } from '@googlemaps/markerclusterer';
  36. import store from '../../index';
  37. import MapSearch from './_/MapSearch';
  38. import ToolMarker from './_/ToolMarker';
  39. import './MapPage.scss';
  40.  
  41. const StyledPaper = styled(Paper)(({ theme }) => ({
  42. position: 'absolute',
  43. zIndex: 1,
  44. width: '600px',
  45. padding: '18px 13px',
  46. top: '60px',
  47. left: '40px',
  48. backgroundColor: theme.palette.background.paper,
  49. boxShadow: theme.shadows[3],
  50. borderRadius: theme.shape.borderRadius,
  51. }));
  52.  
  53. const StyledDropdown = styled(FilterDropdown)({
  54. minWidth: '500px',
  55. });
  56.  
  57. const MapPage = ({ size }: any) => {
  58. const dispatch = useDispatch<AppDispatch>();
  59. const { t } = useTranslation();
  60. const routeLocation = useLocation();
  61. const { companyId } = useGlobalSelectors();
  62. const audits = useSelector((state: any) => state.orm.Audit.itemsById);
  63. const tools = useSelector(rootSelectors.getMapTools);
  64. const locations = useSelector(locationSelectors.getMappedLocations);
  65. const mapType = useSelector((state: any) => state.map.mapType);
  66. const status = useApig(TOOL_MAP_FETCH_REQUEST);
  67. const theme = useTheme();
  68. const {
  69. start, complete,
  70. } = useLoadingBar({ color: theme.palette.primary.main, height: 5, shadow: false });
  71. const [showTools, setShowTools] = useState(true);
  72. const [showLocations, setShowLocations] = useState(true);
  73. const [shownItem, setShownItem] = useState<string | null>(null);
  74. const [searchResult, setSearchResult] = useState<any>(null);
  75.  
  76. const clusterRef = useRef<MarkerClusterer | null>(null);
  77. const googleMarkersRef = useRef<google.maps.Marker[]>([]);
  78.  
  79. const mapOptions = useMemo(() => [
  80. { value: 'showTools', label: t('Map.Dropdown.Tools') },
  81. { value: 'showLocations', label: t('Map.Dropdown.Locations') },
  82. ], [t]);
  83. const mapAllOption = { value: 'all', label: t('Map.Dropdown.All') };
  84.  
  85. useEffect(() => {
  86. if (status) {
  87. if (status === 'loading') {
  88. start();
  89. return;
  90. }
  91. complete();
  92. }
  93. }, [status]);
  94.  
  95. useEffect(() => {
  96. const initializeMapData = async () => {
  97. setSearchResult(null);
  98. dispatch(toolOperations.loadMapTools());
  99. dispatch(locationActions.clearLocationsList());
  100. callTotalLocations(() => dispatch(locationOperations.loadLocations()));
  101. dispatch(mapActions.addMapFilter(mapOptions.map(o => o.value)));
  102. };
  103. initializeMapData();
  104. }, [dispatch, companyId]);
  105.  
  106. const newLocations = useMemo(() => locations.map((loc: any) => ({
  107. ...loc,
  108. audit: audits[loc.audit],
  109. })), [locations, audits]);
  110.  
  111. const makeToggle = (id: string) => () => setShownItem(prev => (prev === id ? null : id));
  112. const { logEvent } = useAnalytics();
  113. const locationParam = routeLocation.pathname.split('/')[2];
  114. const locationId = locationParam || false;
  115.  
  116. const bounds = getMapBounds([], size);
  117. const zoom = !searchResult ? bounds.zoom : Math.max(bounds.zoom - 1, 0);
  118. const center = !searchResult ? bounds.center : {
  119. lat: searchResult.latitude,
  120. lng: searchResult.longitude,
  121. };
  122.  
  123. const handleChangeDropdown = useCallback(
  124. ({ target: { value } }: { target: { value: string[] } }) => {
  125. const wantSelectAll = value.includes('all');
  126. const selectedValues = wantSelectAll ? mapOptions.map(o => o.value) : value;
  127. dispatch(mapActions.addMapFilter(selectedValues));
  128. setShowTools(wantSelectAll || selectedValues.includes('showTools'));
  129. setShowLocations(wantSelectAll || selectedValues.includes('showLocations'));
  130. },
  131. [dispatch, mapOptions],
  132. );
  133.  
  134. const handleInitialize = ({ map, maps }: any) => {
  135. if (clusterRef.current) clusterRef.current.clearMarkers();
  136. googleMarkersRef.current.forEach(marker => marker.setMap(null));
  137. googleMarkersRef.current = [];
  138.  
  139. const markers: google.maps.Marker[] = [];
  140.  
  141. const createMarker = (position: google.maps.LatLngLiteral, content: HTMLElement) => {
  142. const scaledSize = maps?.Size ? new maps.Size(40, 40) : undefined;
  143. const marker = new google.maps.Marker({
  144. position,
  145. map: null,
  146. icon: {
  147. url: 'https://maps.google.com/mapfiles/ms/icons/red-dot.png',
  148. scaledSize,
  149. },
  150. });
  151. markers.push(marker);
  152. };
  153.  
  154. if (showTools && !locationId) {
  155. tools.forEach((tool: any) => {
  156. const linkedDumbTool = toolSelectors.getLinkedDumbToolById(store.getState(), tool.thingId);
  157. const node = document.createElement('div');
  158. createRoot(node).render(
  159. <ToolMarker
  160. tool={tool}
  161. linkedDumbTool={linkedDumbTool}
  162. showPopup={tool.thingId === shownItem}
  163. toggle={makeToggle(tool.thingId)}
  164. action={id => dispatch(modalOperations.showModal(EDIT_TOOL, { id }))}
  165. />,
  166. );
  167. createMarker({ lat: +tool.latitude, lng: +tool.longitude }, node);
  168. });
  169. }
  170.  
  171. if (showLocations) {
  172. const filtered = locationId
  173. ? newLocations.filter((loc: any) => loc.locationId === locationId)
  174. : newLocations;
  175. filtered.forEach((loc: any) => {
  176. const node = document.createElement('div');
  177. createRoot(node).render(
  178. <LocationMarker
  179. location={loc}
  180. lat={+loc.latitude}
  181. lng={+loc.longitude}
  182. showPopup={loc.locationId === shownItem}
  183. toggle={makeToggle(loc.locationId)}
  184. />,
  185. );
  186. createMarker({ lat: +loc.latitude, lng: +loc.longitude }, node);
  187. });
  188. }
  189.  
  190. if (searchResult) {
  191. const { locationName, ...result } = searchResult;
  192. const node = document.createElement('div');
  193. createRoot(node).render(
  194. <SearchResultMarker
  195. lat={+searchResult.latitude}
  196. lng={+searchResult.longitude}
  197. onSearchClear={() => setSearchResult(null)}
  198. onCreate={() => {
  199. logEvent('search_result_popup_assignment');
  200. dispatch(modalOperations.showModal(LOCATION, {
  201. location: result,
  202. showModal: true,
  203. isNewLocation: true,
  204. onSuccess: () => setSearchResult(null),
  205. }));
  206. }}
  207. description={locationName}
  208. />,
  209. );
  210. createMarker({ lat: +searchResult.latitude, lng: +searchResult.longitude }, node);
  211. }
  212.  
  213. googleMarkersRef.current = markers;
  214. clusterRef.current = new MarkerClusterer({ map, markers: googleMarkersRef.current });
  215. };
  216.  
  217. return (
  218. <section className="map-page">
  219. <HideApigLoading />
  220. <StyledPaper>
  221. <Grid container spacing={1} sx={{ alignItems: 'center' }}>
  222. <Grid item xs={7}>
  223. <MapSearch onSelect={value => setSearchResult(value)} />
  224. </Grid>
  225. <Grid item xs={5}>
  226. <StyledDropdown
  227. options={mapOptions}
  228. title={t('Map.Dropdown.Title')}
  229. handleChange={handleChangeDropdown}
  230. allOption={mapAllOption}
  231. selectedValues={mapType}
  232. />
  233. </Grid>
  234. </Grid>
  235. </StyledPaper>
  236. <Map
  237. center={center}
  238. zoom={zoom}
  239. options={(maps: any) => ({
  240. mapTypeControl: true,
  241. mapTypeControlOptions: {
  242. position: maps.ControlPosition.LEFT_BOTTOM,
  243. },
  244. fullscreenControlOptions: {
  245. position: maps.ControlPosition.RIGHT_BOTTOM,
  246. },
  247. })}
  248. onInitialize={handleInitialize}
  249. />
  250. </section>
  251. );
  252. };
  253.  
  254. export default sizeMe({ monitorHeight: true })(MapPage);
  255.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement