Files
porthole/apps/web/app/map/page.tsx
2026-02-04 18:13:30 -08:00

96 lines
2.5 KiB
TypeScript

"use client";
import L from "leaflet";
import { useEffect, useRef, useState } from "react";
import { MapContainer, TileLayer, Marker, Popup, useMap } from "react-leaflet";
type GeoPoint = {
id: string;
gps_lat: number | null;
gps_lon: number | null;
};
function MapContent({ points, error }: { points: GeoPoint[]; error: string | null }) {
const map = useMap();
const markersRef = useRef<any[]>([]);
useEffect(() => {
markersRef.current.forEach((marker) => marker.remove());
markersRef.current = [];
if (points.length === 0) return;
points.forEach((point) => {
if (point.gps_lat === null || point.gps_lon === null) return;
const marker = L.marker([point.gps_lat, point.gps_lon]);
marker.addTo(map);
markersRef.current.push(marker);
});
if (points.length > 0) {
const group = L.featureGroup(markersRef.current);
map.fitBounds(group.getBounds().pad(0.1));
}
}, [points, map]);
return null;
}
export default function MapPage() {
const [points, setPoints] = useState<GeoPoint[]>([]);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch("/api/geo")
.then((res) => {
if (!res.ok) throw new Error("Failed to fetch geo points");
return res.json();
})
.then((data) => {
setPoints(data);
})
.catch((err) => {
setError(err instanceof Error ? err.message : "Unknown error");
})
.finally(() => {
setLoading(false);
});
}, []);
return (
<main style={{ padding: 16, display: "grid", gap: 16, height: "calc(100vh - 32px)" }}>
<header>
<h1 style={{ marginTop: 0 }}>Map</h1>
</header>
{loading ? (
<div style={{ textAlign: "center", padding: 40 }}>
Loading map...
</div>
) : error ? (
<div style={{ textAlign: "center", padding: 40, color: "#b00" }}>
Error: {error}
</div>
) : points.length === 0 ? (
<div style={{ textAlign: "center", padding: 40, color: "#666" }}>
No GPS points available
</div>
) : (
<div style={{ flex: 1, minHeight: 0 }}>
<MapContainer
style={{ height: "100%", width: "100%" }}
boundsOptions={{ padding: [50, 50] }}
>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<MapContent points={points} error={error} />
</MapContainer>
</div>
)}
</main>
);
}