feat(frontend): maps mvp | close #283
This commit is contained in:
parent
1adbabe654
commit
3cd47b155c
|
|
@ -35,15 +35,15 @@ export const items = [
|
||||||
// </SvgIcon>
|
// </SvgIcon>
|
||||||
// )
|
// )
|
||||||
// },
|
// },
|
||||||
// {
|
{
|
||||||
// title: 'Map',
|
title: 'Map',
|
||||||
// path: '/map',
|
path: '/map',
|
||||||
// icon: (
|
icon: (
|
||||||
// <SvgIcon fontSize="small">
|
<SvgIcon fontSize="small">
|
||||||
// <MapIcon/>
|
<MapIcon/>
|
||||||
// </SvgIcon>
|
</SvgIcon>
|
||||||
// ),
|
),
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
title: 'Credentials',
|
title: 'Credentials',
|
||||||
path: '/credentials',
|
path: '/credentials',
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,9 @@ export const SideNav = (props) => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{items.map((item) => {
|
{items.map((item) => {
|
||||||
|
if (item.title == "Map" && process.env.NEXT_PUBLIC_ENTERPRISE_VERSION != "true"){
|
||||||
|
return
|
||||||
|
}
|
||||||
const active = isItemActive(pathname, item.path);
|
const active = isItemActive(pathname, item.path);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { createTheme } from 'src/theme';
|
||||||
import { createEmotionCache } from 'src/utils/create-emotion-cache';
|
import { createEmotionCache } from 'src/utils/create-emotion-cache';
|
||||||
import 'simplebar-react/dist/simplebar.min.css';
|
import 'simplebar-react/dist/simplebar.min.css';
|
||||||
import { WsProvider } from 'src/contexts/socketio-context';
|
import { WsProvider } from 'src/contexts/socketio-context';
|
||||||
|
import '../utils/map.css';
|
||||||
|
|
||||||
const clientSideEmotionCache = createEmotionCache();
|
const clientSideEmotionCache = createEmotionCache();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,120 @@
|
||||||
import Head from 'next/head';
|
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 { Layout as DashboardLayout } from 'src/layouts/dashboard/layout';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import mapStyles from '../utils/mapStyles.json';
|
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 (
|
||||||
|
<OverlayView
|
||||||
|
position={props.anchorPosition}
|
||||||
|
mapPaneName={OverlayView.OVERLAY_MOUSE_TARGET}
|
||||||
|
getPixelPositionOffset={getPixelPositionOffset(props.markerPixelOffset)}
|
||||||
|
>
|
||||||
|
<div className="popup-tip-anchor">
|
||||||
|
<div className="popup-bubble-anchor">
|
||||||
|
<div className="popup-bubble-content">{props.content}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</OverlayView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const Page = () => {
|
const Page = () => {
|
||||||
|
|
||||||
const libraries = useMemo(() => ['places'], []);
|
const libraries = useMemo(() => ['places'], []);
|
||||||
|
|
||||||
const [mapCenter, setMapCenter] = useState(null);
|
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(()=> {
|
useEffect(()=> {
|
||||||
// Check if geolocation is supported by the browser
|
fetchMarkers();
|
||||||
if ("geolocation" in navigator) {
|
// Check if geolocation is supported by the browser
|
||||||
// Prompt user for permission to access their location
|
if ("geolocation" in navigator) {
|
||||||
navigator.geolocation.getCurrentPosition(
|
// Prompt user for permission to access their location
|
||||||
// Get the user's latitude and longitude coordinates
|
navigator.geolocation.getCurrentPosition(
|
||||||
// Success callback function
|
// Get the user's latitude and longitude coordinates
|
||||||
function(position) {
|
// Success callback function
|
||||||
// Update the map with the user's new location
|
function(position) {
|
||||||
setMapCenter({
|
// Update the map with the user's new location
|
||||||
lat: position.coords.latitude,
|
setMapCenter({
|
||||||
lng: position.coords.longitude,
|
lat: position.coords.latitude,
|
||||||
})
|
lng: position.coords.longitude,
|
||||||
},
|
})
|
||||||
// Error callback function
|
},
|
||||||
function(error) {
|
// Error callback function
|
||||||
// Handle errors, e.g. user denied location sharing permissions
|
function(error) {
|
||||||
console.error("Error getting user location:", 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
|
} else {
|
||||||
console.error("Geolocation is not supported by this browser.");
|
// Geolocation is not supported by the browser
|
||||||
}
|
console.error("Geolocation is not supported by this browser.");
|
||||||
|
}
|
||||||
},[])
|
},[])
|
||||||
|
|
||||||
const mapOptions = useMemo(
|
const mapOptions = useMemo(
|
||||||
|
|
@ -59,7 +140,7 @@ const Page = () => {
|
||||||
return <p>Loading...</p>;
|
return <p>Loading...</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ( mapCenter &&
|
return ( mapCenter && markers &&
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>
|
<title>
|
||||||
|
|
@ -73,7 +154,51 @@ const Page = () => {
|
||||||
mapContainerStyle={{ width: '100%', height: '100%' }}
|
mapContainerStyle={{ width: '100%', height: '100%' }}
|
||||||
onLoad={() => console.log('Map Component Loaded...')}
|
onLoad={() => console.log('Map Component Loaded...')}
|
||||||
clickableIcons={false}
|
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>
|
||||||
</>
|
</>
|
||||||
)};
|
)};
|
||||||
|
|
||||||
|
|
|
||||||
47
frontend/src/utils/map.css
Normal file
47
frontend/src/utils/map.css
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user