'use client';
import { useEffect, useMemo, useState } from 'react';
import { MapContainer, TileLayer, Marker, Popup, useMap } from 'react-leaflet';
import { Collection, Supplier } from '@/lib/types';
import { formatCurrency, calculateDistance } from '@/lib/utils';
import { Navigation, Check, AlertTriangle } from 'lucide-react';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
// Fix Leaflet default markers in Next.js
const defaultIcon = L.icon({
iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png',
iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41],
});
const currentLocationIcon = L.divIcon({
className: 'custom-div-icon',
html: `
`,
iconSize: [20, 20],
iconAnchor: [10, 10],
});
interface MapViewProps {
collections: Collection[];
onNavigate: (lat: number, lng: number, name: string) => void;
}
function MapBounds({ collections }: { collections: Collection[] }) {
const map = useMap();
useEffect(() => {
const validPoints = collections
.filter((c) => c.expand?.supplier?.latitude && c.expand?.supplier?.longitude)
.map((c) => [c.expand!.supplier!.latitude!, c.expand!.supplier!.longitude!] as [number, number]);
if (validPoints.length > 0) {
const bounds = L.latLngBounds(validPoints);
map.fitBounds(bounds, { padding: [50, 50], maxZoom: 15 });
}
}, [map, collections]);
return null;
}
export default function CollectionMap({ collections, onNavigate }: MapViewProps) {
const [userLocation, setUserLocation] = useState<[number, number] | null>(null);
const [selectedSupplier, setSelectedSupplier] = useState(null);
useEffect(() => {
if ('geolocation' in navigator) {
navigator.geolocation.getCurrentPosition(
(pos) => {
setUserLocation([pos.coords.latitude, pos.coords.longitude]);
},
(error) => console.error('Could not get location:', error),
{ enableHighAccuracy: true, timeout: 10000 }
);
const watchId = navigator.geolocation.watchPosition(
(pos) => {
setUserLocation([pos.coords.latitude, pos.coords.longitude]);
},
(error) => console.error('Watch error:', error),
{ enableHighAccuracy: true }
);
return () => navigator.geolocation.clearWatch(watchId);
}
}, []);
// Group collections by supplier
const suppliers = useMemo(() => {
const map = new Map();
collections.forEach((collection) => {
if (!collection.expand?.supplier) return;
const supplier = collection.expand.supplier;
if (!supplier.latitude || !supplier.longitude) return;
const key = supplier.id;
if (!map.has(key)) {
map.set(key, { supplier, collections: [] });
}
map.get(key)!.collections.push(collection);
});
return Array.from(map.values());
}, [collections]);
const defaultCenter: [number, number] = [-33.9249, 18.4241]; // Cape Town default
const center = userLocation || defaultCenter;
const getSupplierDistance = (supplier: Supplier) => {
if (!userLocation || !supplier.latitude || !supplier.longitude) return null;
return calculateDistance(
userLocation[0],
userLocation[1],
supplier.latitude,
supplier.longitude
);
};
return (
{userLocation && (
Your Location
)}
{suppliers.map(({ supplier, collections: supplierCollections }) => {
const distance = getSupplierDistance(supplier);
const hasProblem = supplierCollections.some((c) => c.status === 'problem');
return (
setSelectedSupplier(supplier.id),
}}
>
{supplier.suppliername}
{supplier.address}
{distance && (
{distance.toFixed(1)} km away
)}
{supplierCollections.map((c) => (
{c.status === 'problem' && (
)}
{c.is_manual
? c.description
: c.expand?.order_item?.description || 'Unknown'}
Qty: {c.quantity} {c.unit || c.expand?.order_item?.unit || 'units'}
))}
);
})}
{/* Legend overlay */}
);
}