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)
-
-
-
-
- | Timestamp |
- Latitude |
- Longitude |
-
-
-
- {locationHistory.map((location, index) => (
-
- |
- {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
}
+
+
+
+ );
+ })}
+
+
+ );
+}