From 3cd47b155c2ff45d86685d2c4771b6871db79c53 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Fri, 5 Jul 2024 15:50:50 -0300 Subject: [PATCH] feat(frontend): maps mvp | close #283 --- frontend/src/layouts/dashboard/config.js | 18 +-- frontend/src/layouts/dashboard/side-nav.js | 3 + frontend/src/pages/_app.js | 1 + frontend/src/pages/map.js | 177 ++++++++++++++++++--- frontend/src/utils/map.css | 47 ++++++ 5 files changed, 211 insertions(+), 35 deletions(-) create mode 100644 frontend/src/utils/map.css diff --git a/frontend/src/layouts/dashboard/config.js b/frontend/src/layouts/dashboard/config.js index 171482a..f2e7fff 100644 --- a/frontend/src/layouts/dashboard/config.js +++ b/frontend/src/layouts/dashboard/config.js @@ -35,15 +35,15 @@ export const items = [ // // ) // }, - // { - // title: 'Map', - // path: '/map', - // icon: ( - // - // - // - // ), - // }, + { + title: 'Map', + path: '/map', + icon: ( + + + + ), + }, { title: 'Credentials', path: '/credentials', diff --git a/frontend/src/layouts/dashboard/side-nav.js b/frontend/src/layouts/dashboard/side-nav.js index ce2e646..e378c2a 100644 --- a/frontend/src/layouts/dashboard/side-nav.js +++ b/frontend/src/layouts/dashboard/side-nav.js @@ -112,6 +112,9 @@ export const SideNav = (props) => { }} > {items.map((item) => { + if (item.title == "Map" && process.env.NEXT_PUBLIC_ENTERPRISE_VERSION != "true"){ + return + } const active = isItemActive(pathname, item.path); return ( diff --git a/frontend/src/pages/_app.js b/frontend/src/pages/_app.js index 7bc21fb..3c4bdc9 100644 --- a/frontend/src/pages/_app.js +++ b/frontend/src/pages/_app.js @@ -10,6 +10,7 @@ import { createTheme } from 'src/theme'; import { createEmotionCache } from 'src/utils/create-emotion-cache'; import 'simplebar-react/dist/simplebar.min.css'; import { WsProvider } from 'src/contexts/socketio-context'; +import '../utils/map.css'; const clientSideEmotionCache = createEmotionCache(); diff --git a/frontend/src/pages/map.js b/frontend/src/pages/map.js index 24a111c..18fd812 100644 --- a/frontend/src/pages/map.js +++ b/frontend/src/pages/map.js @@ -1,39 +1,120 @@ import Head from 'next/head'; -import { GoogleMap, useLoadScript } from "@react-google-maps/api" +import { GoogleMap, useLoadScript, Marker, OverlayView } from "@react-google-maps/api" import { Layout as DashboardLayout } from 'src/layouts/dashboard/layout'; import { useEffect, useMemo, useState } from 'react'; import mapStyles from '../utils/mapStyles.json'; +const getPixelPositionOffset = pixelOffset => (width, height) => ({ + x: -(width / 2) + pixelOffset.x, + y: -(height / 2) + pixelOffset.y +}); + +const Popup = props => { + return ( + +
+
+
{props.content}
+
+
+
+ ); +}; + const Page = () => { const libraries = useMemo(() => ['places'], []); const [mapCenter, setMapCenter] = useState(null); + const [markers, setMarkers] = useState([]); + const [activeMarker, setActiveMarker] = useState(null); + const [activeMarkerdata, setActiveMarkerdata] = useState(null); + + const fetchMarkers = async () => { + + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("Authorization", localStorage.getItem("token")); + + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + }; + + let result = await fetch(`${process.env.NEXT_PUBLIC_REST_ENDPOINT || ""}/api/map`, requestOptions) + + if (result.status == 200) { + const content = await result.json() + setMarkers(content) + }else if (result.status == 403) { + console.log("num tenx permissão, seu boca de sandália") + return router.push("/403") + }else if (result.status == 401){ + console.log("taix nem autenticado, sai fora oh") + return router.push("/auth/login") + } else { + console.log("agora quebrasse ux córno mô quiridu") + const content = await result.json() + throw new Error(content); + } + } + + const fetchActiveMarkerData = async (id) => { + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("Authorization", localStorage.getItem("token")); + + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + }; + + let result = await fetch(`${process.env.NEXT_PUBLIC_REST_ENDPOINT || ""}/api/device?id=`+id, requestOptions) + + if (result.status == 200) { + const content = await result.json() + setActiveMarkerdata(content) + }else if (result.status == 403) { + return router.push("/403") + }else if (result.status == 401){ + return router.push("/auth/login") + } else { + console.log("no device info found") + const content = await result.json() + } + } useEffect(()=> { - // Check if geolocation is supported by the browser - if ("geolocation" in navigator) { - // Prompt user for permission to access their location - navigator.geolocation.getCurrentPosition( - // Get the user's latitude and longitude coordinates - // Success callback function - function(position) { - // Update the map with the user's new location - setMapCenter({ - lat: position.coords.latitude, - lng: position.coords.longitude, - }) - }, - // Error callback function - function(error) { - // Handle errors, e.g. user denied location sharing permissions - console.error("Error getting user location:", error); - } - ); - } else { - // Geolocation is not supported by the browser - console.error("Geolocation is not supported by this browser."); - } + fetchMarkers(); + // Check if geolocation is supported by the browser + if ("geolocation" in navigator) { + // Prompt user for permission to access their location + navigator.geolocation.getCurrentPosition( + // Get the user's latitude and longitude coordinates + // Success callback function + function(position) { + // Update the map with the user's new location + setMapCenter({ + lat: position.coords.latitude, + lng: position.coords.longitude, + }) + }, + // Error callback function + function(error) { + // Handle errors, e.g. user denied location sharing permissions + console.error("Error getting user location:", error); + } + ); + } else { + // Geolocation is not supported by the browser + console.error("Geolocation is not supported by this browser."); + } },[]) const mapOptions = useMemo( @@ -59,7 +140,7 @@ const Page = () => { return

Loading...

; } - return ( mapCenter && + return ( mapCenter && markers && <> @@ -73,7 +154,51 @@ const Page = () => { mapContainerStyle={{ width: '100%', height: '100%' }} onLoad={() => console.log('Map Component Loaded...')} clickableIcons={false} - /> + > + { + markers.map((marker, index) => ( + <Marker + key={index} + position={{ lat: marker.coordinates.lat, lng: marker.coordinates.lng }} + icon={{ + url: marker.img, + scaledSize: new window.google.maps.Size(50, 50), + anchor: new window.google.maps.Point(25, 25), + }} + draggable={false} + clickable={true} + onClick={() => { + setActiveMarkerdata(null); + if (activeMarker?.sn === marker.sn) { + setActiveMarker(null); + return; + } + fetchActiveMarkerData(marker.sn); + setActiveMarker({ + sn: marker.sn, + position: { lat: marker.coordinates.lat, lng: marker.coordinates.lng } + }); + }} + > + </Marker> + )) + } + {activeMarker && + <Popup + anchorPosition={activeMarker.position} + markerPixelOffset={{ x: 0, y: -32 }} + content={activeMarkerdata ? + <div> + <div>SN: {activeMarker.sn}</div> + <div> + <div>Model: {activeMarkerdata.Model?activeMarkerdata.Model:activeMarkerdata.ProductClass}</div> + <div>Alias: {activeMarkerdata.Alias}</div> + <div>Status: {activeMarkerdata.Status == 2 ? <span style={{color:"green"}}>online</span> : <span style={{color:"green"}}>offline</span>}</div> + </div> + </div> + : <p>no device info found</p>} + />} + </GoogleMap> </> )}; diff --git a/frontend/src/utils/map.css b/frontend/src/utils/map.css new file mode 100644 index 0000000..55a1725 --- /dev/null +++ b/frontend/src/utils/map.css @@ -0,0 +1,47 @@ +/* The location pointed to by the popup tip. */ +.popup-tip-anchor { + height: 0; + position: absolute; + /* The max width of the info window. */ + width: 200px; + } + /* The bubble is anchored above the tip. */ + .popup-bubble-anchor { + position: absolute; + width: 100%; + bottom: /* TIP_HEIGHT= */ 8px; + left: 0; + } + /* Draw the tip. */ + .popup-bubble-anchor::after { + content: ""; + position: absolute; + top: 0; + left: 0; + /* Center the tip horizontally. */ + transform: translate(-50%, 0); + /* The tip is a https://css-tricks.com/snippets/css/css-triangle/ */ + width: 0; + height: 0; + /* The tip is 8px high, and 12px wide. */ + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: /* TIP_HEIGHT= */ 8px solid white; + } + /* The popup bubble itself. */ + .popup-bubble-content { + position: absolute; + top: 0; + left: 0; + transform: translate(-50%, -100%); + /* Style the info window. */ + background-color: white; + padding: 5px; + border-radius: 5px; + font-family: sans-serif; + font-size: 14px ; + overflow-y: auto; + max-height: 80px; + box-shadow: 0px 2px 10px 1px rgba(0, 0, 0, 0.5); + } + \ No newline at end of file