diff --git a/meshexplorer/src/app/(app)/meshcore/node/[publicKey]/page.tsx b/meshexplorer/src/app/(app)/meshcore/node/[publicKey]/page.tsx index 6f420ff..f4aaa8f 100644 --- a/meshexplorer/src/app/(app)/meshcore/node/[publicKey]/page.tsx +++ b/meshexplorer/src/app/(app)/meshcore/node/[publicKey]/page.tsx @@ -1,5 +1,7 @@ "use client"; +import { useState } from "react"; +import dynamic from "next/dynamic"; import { useParams } from "next/navigation"; import Link from "next/link"; import moment from "moment"; @@ -13,6 +15,9 @@ import { useNodeData, type NodeData, type NodeInfo, type Advert, type LocationHi import { ArrowRightEndOnRectangleIcon, ArrowRightStartOnRectangleIcon } from "@heroicons/react/24/outline"; import { RegionProvider } from "@/contexts/RegionContext"; +// Leaflet needs `window`, so load the location-history map client-side only. +const LocationHistoryMap = dynamic(() => import("@/components/LocationHistoryMap"), { ssr: false }); + // Interfaces are now imported from useNodeData hook // Function to determine node type based on capabilities @@ -27,6 +32,7 @@ export default function MeshcoreNodePage() { const params = useParams(); const publicKey = params.publicKey as string; const { config } = useConfig(); + const [showAllAdverts, setShowAllAdverts] = useState(false); // Use TanStack Query for node data const { @@ -353,7 +359,9 @@ export default function MeshcoreNodePage() {

Recent Adverts

-

Latest {recentAdverts.length} adverts

+

+ Showing {Math.min(showAllAdverts ? recentAdverts.length : 5, recentAdverts.length)} of {recentAdverts.length} adverts +

{recentAdverts.length === 0 ? ( @@ -361,9 +369,19 @@ export default function MeshcoreNodePage() { No adverts found
) : ( - recentAdverts.map((advert) => ( - - )) + <> + {(showAllAdverts ? recentAdverts : recentAdverts.slice(0, 5)).map((advert) => ( + + ))} + {recentAdverts.length > 5 && ( + + )} + )}
@@ -374,32 +392,13 @@ export default function MeshcoreNodePage() {

Location History

Recent location updates (last 30 days)

-
- - - - - - - - - - {locationHistory.map((location, index) => ( - - - - - - ))} - -
TimestampLatitudeLongitude
- {moment.utc(location.mesh_timestamp).format('MM-DD HH:mm:ss')} - - {location.latitude.toFixed(6)} - - {location.longitude.toFixed(6)} -
-
+ {locationHistory.length === 0 ? ( +
+ No location data +
+ ) : ( + + )} diff --git a/meshexplorer/src/components/LocationHistoryMap.tsx b/meshexplorer/src/components/LocationHistoryMap.tsx new file mode 100644 index 0000000..a698542 --- /dev/null +++ b/meshexplorer/src/components/LocationHistoryMap.tsx @@ -0,0 +1,82 @@ +"use client"; +import { useEffect } from "react"; +import { MapContainer, TileLayer, CircleMarker, Polyline, Popup, useMap } from "react-leaflet"; +import moment from "moment"; +import 'leaflet/dist/leaflet.css'; +import L from "leaflet"; +import { type LocationHistory } from "@/hooks/useNodeData"; + +interface LocationHistoryMapProps { + locations: LocationHistory[]; +} + +// Fit the map to all location points on mount / when the points change. +function FitBounds({ coords }: { coords: [number, number][] }) { + const map = useMap(); + useEffect(() => { + if (coords.length === 0) return; + if (coords.length === 1) { + map.setView(coords[0], 14); + return; + } + map.fitBounds(L.latLngBounds(coords), { padding: [24, 24] }); + }, [map, coords]); + return null; +} + +export default function LocationHistoryMap({ locations }: LocationHistoryMapProps) { + // locations come newest-first; coords reversed to chronological for the track line. + const coords = locations.map((l) => [l.latitude, l.longitude] as [number, number]); + const trackCoords = [...coords].reverse(); + + return ( + + + {trackCoords.length > 1 && ( + + )} + {locations.map((location, index) => { + const isLatest = index === 0; + return ( + + +
+
+ {moment.utc(location.mesh_timestamp).format('YYYY-MM-DD HH:mm:ss')} UTC +
+
+ {location.latitude.toFixed(6)}, {location.longitude.toFixed(6)} +
+ {isLatest &&
Most recent
} +
+
+
+ ); + })} + +
+ ); +}