Add interactive Leaflet burrow map + fix stream API field mapping

This commit is contained in:
BizzleBot
2026-02-19 18:26:07 +00:00
parent 857f5a2396
commit 581ffde7fc
5 changed files with 150 additions and 21 deletions
+84
View File
@@ -0,0 +1,84 @@
'use client';
import React, { useEffect, useState } from 'react';
import { MapContainer, TileLayer, Marker, Popup, CircleMarker } from 'react-leaflet';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
interface Burrow {
id: string;
gps_lat: number;
gps_lng: number;
status: string;
location_description: string;
}
// Fix default marker icons in Next.js
const activeIcon = new L.DivIcon({
className: '',
html: '<div style="width:14px;height:14px;border-radius:50%;background:#0082a7;box-shadow:0 0 10px rgba(0,130,167,0.8);border:2px solid rgba(255,255,255,0.3)"></div>',
iconSize: [14, 14],
iconAnchor: [7, 7],
});
const inactiveIcon = new L.DivIcon({
className: '',
html: '<div style="width:12px;height:12px;border-radius:50%;background:#57534e;border:2px solid rgba(255,255,255,0.15)"></div>',
iconSize: [12, 12],
iconAnchor: [6, 6],
});
export default function BurrowMap() {
const [burrows, setBurrows] = useState<Burrow[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
fetch(`${apiUrl}/api/burrows`)
.then((r) => r.json())
.then((data) => { setBurrows(data); setLoading(false); })
.catch(() => setLoading(false));
}, []);
if (loading) {
return (
<div className="w-full h-full flex items-center justify-center text-stone-500">
<div className="animate-spin w-8 h-8 border-2 border-teal border-t-transparent rounded-full" />
</div>
);
}
// Cape Coral center
const center: [number, number] = [26.6029, -81.9595];
return (
<MapContainer
center={center}
zoom={13}
className="w-full h-full rounded-3xl"
style={{ background: '#0a1a15' }}
attributionControl={false}
>
<TileLayer
url="https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png"
attribution='&copy; <a href="https://carto.com/">CARTO</a>'
/>
{burrows.map((b) => (
<Marker
key={b.id}
position={[b.gps_lat, b.gps_lng]}
icon={b.status === 'active' ? activeIcon : inactiveIcon}
>
<Popup>
<div className="text-sm space-y-1" style={{ color: '#1a1a1a' }}>
<div className="font-bold">{b.status === 'active' ? '🟢 Active' : '⚫ Inactive'} Burrow</div>
<div>{b.location_description}</div>
<div className="text-xs text-gray-500">
{b.gps_lat.toFixed(4)}, {b.gps_lng.toFixed(4)}
</div>
</div>
</Popup>
</Marker>
))}
</MapContainer>
);
}
+11 -21
View File
@@ -1,4 +1,5 @@
import React from 'react';
import dynamic from 'next/dynamic';
import { Map } from 'lucide-react';
import type { Metadata } from 'next';
@@ -8,7 +9,14 @@ export const metadata: Metadata = {
description: 'Interactive map of burrowing owl burrows in Cape Coral, Florida.',
};
const BurrowMap = dynamic(() => import('./BurrowMap'), {
ssr: false,
loading: () => (
<div className="w-full h-full flex items-center justify-center text-stone-500">
<div className="animate-spin w-8 h-8 border-2 border-teal border-t-transparent rounded-full" />
</div>
),
});
export default function MapPage() {
return (
@@ -23,27 +31,9 @@ export default function MapPage() {
</p>
</header>
{/* Map placeholder — interactive Leaflet map requires client component */}
{/* Interactive Leaflet Map */}
<div className="relative rounded-3xl overflow-hidden bg-surfaceGreen border border-white/5 shadow-2xl" style={{ height: 600 }}>
<div className="absolute inset-0 flex flex-col items-center justify-center gap-6 text-stone-500">
<div className="text-8xl">🗺</div>
<div className="text-center space-y-2">
<p className="text-xl font-bold text-stone-300">Interactive Burrow Map</p>
<p className="text-sm">
Leaflet map with burrow markers loading from{' '}
<code className="text-teal text-xs bg-black/30 px-2 py-0.5 rounded">/api/burrows</code>
</p>
</div>
</div>
{/* Decorative grid pattern */}
<div
className="absolute inset-0 opacity-5"
style={{
backgroundImage:
'linear-gradient(rgba(0,130,167,0.5) 1px, transparent 1px), linear-gradient(90deg, rgba(0,130,167,0.5) 1px, transparent 1px)',
backgroundSize: '40px 40px',
}}
/>
<BurrowMap />
</div>
{/* Legend */}