Files
mini/src/components/admin/CampaignMap.tsx

132 lines
4.3 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { MapContainer, TileLayer, Marker, Popup, CircleMarker } from 'react-leaflet';
import * as turf from '@turf/turf';
import 'leaflet/dist/leaflet.css';
/**
* Campaign Map Component
* Visualize geospatial content coverage
*/
interface Location {
id: string;
lat: number;
lng: number;
city?: string;
state?: string;
content_generated?: boolean;
}
interface CampaignMapProps {
geoData?: {
type: string;
features: any[];
};
defaultCenter?: [number, number];
defaultZoom?: number;
}
export default function CampaignMap({
geoData,
defaultCenter = [39.8283, -98.5795], // Center of USA
defaultZoom = 4
}: CampaignMapProps) {
const [locations, setLocations] = useState<Location[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
fetchLocations();
}, []);
async function fetchLocations() {
try {
const token = localStorage.getItem('godToken') || '';
const res = await fetch('/api/god/sql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-God-Token': token
},
body: JSON.stringify({
query: `
SELECT
id::text,
ST_Y(location::geometry) as lat,
ST_X(location::geometry) as lng,
city,
state,
content_generated
FROM geo_locations
WHERE location IS NOT NULL
LIMIT 1000
`
})
});
if (!res.ok) {
setIsLoading(false);
return;
}
const data = await res.json();
setLocations(data.rows || []);
setIsLoading(false);
} catch (error) {
console.error('Failed to fetch locations:', error);
setIsLoading(false);
}
}
if (isLoading) {
return (
<div className="w-full h-[500px] bg-slate-900 rounded-xl flex items-center justify-center text-slate-500">
Loading map data...
</div>
);
}
return (
<div className="w-full h-[500px] rounded-xl overflow-hidden border border-slate-800">
<MapContainer
center={defaultCenter}
zoom={defaultZoom}
style={{ height: '100%', width: '100%' }}
className="z-0"
>
{/* Tile Layer (Map Background) */}
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
/>
{/* Location Markers */}
{locations.map((location) => (
<CircleMarker
key={location.id}
center={[location.lat, location.lng]}
radius={location.content_generated ? 8 : 5}
fillColor={location.content_generated ? '#10b981' : '#3b82f6'}
color={location.content_generated ? '#059669' : '#2563eb'}
weight={1}
opacity={0.8}
fillOpacity={0.6}
>
<Popup>
<div className="text-sm">
<strong>{location.city || 'Unknown'}, {location.state || '??'}</strong>
<br />
Status: {location.content_generated ? '✅ Generated' : '⏳ Pending'}
<br />
<span className="text-xs text-gray-500">
{location.lat.toFixed(4)}, {location.lng.toFixed(4)}
</span>
</div>
</Popup>
</CircleMarker>
))}
</MapContainer>
</div>
);
}