'use client';
import { useEffect, useMemo, useState, useCallback, useRef } from 'react';
import { MapContainer, TileLayer, Marker, Popup, Polyline, useMap } from 'react-leaflet';
import { OrderItem, Supplier, DriverLocation } from '@/lib/types';
import { formatCurrency, calculateDistance, parseCoordinates } from '@/lib/utils';
import { getDriverTrail, isAuthenticated, getCurrentUser } from '@/lib/pocketbase';
import { Navigation, AlertTriangle, Route, Crosshair, Layers } from 'lucide-react';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
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: [22, 22],
iconAnchor: [11, 11],
});
interface MapViewProps {
items: OrderItem[];
onNavigate: (lat: number, lng: number, name: string) => void;
}
function MapBounds({ items }: { items: OrderItem[] }) {
const map = useMap();
useEffect(() => {
const validPoints = items
.map((i) => {
const coords = parseCoordinates(i.expand?.supplier?.coordinates);
return coords ? [coords.latitude, coords.longitude] as [number, number] : null;
})
.filter((point): point is [number, number] => point !== null);
if (validPoints.length > 0) {
const bounds = L.latLngBounds(validPoints);
map.fitBounds(bounds, { padding: [60, 60], maxZoom: 15 });
}
}, [map, items]);
return null;
}
function RecenterButton({ center }: { center: [number, number] }) {
const map = useMap();
return (
);
}
export default function CollectionMap({ items, onNavigate }: MapViewProps) {
const [userLocation, setUserLocation] = useState<[number, number] | null>(null);
const [trail, setTrail] = useState<[number, number][]>([]);
const [showTrail, setShowTrail] = useState(true);
const intervalRef = useRef | null>(null);
// Get current position
useEffect(() => {
if ('geolocation' in navigator) {
navigator.geolocation.getCurrentPosition(
(pos) => setUserLocation([pos.coords.latitude, pos.coords.longitude]),
() => {},
{ enableHighAccuracy: false, timeout: 30000 }
);
}
}, []);
// Fetch trail data periodically
const fetchTrail = useCallback(async () => {
if (!isAuthenticated()) return;
const user = getCurrentUser();
if (!user) return;
const data = await getDriverTrail(user.id);
const points = data
.filter((loc) => loc.latitude && loc.longitude)
.map((loc) => [loc.latitude, loc.longitude] as [number, number]);
if (points.length > 0) {
setTrail(points);
}
}, []);
useEffect(() => {
fetchTrail();
intervalRef.current = setInterval(fetchTrail, 60000);
return () => {
if (intervalRef.current) clearInterval(intervalRef.current);
};
}, [fetchTrail]);
// Group by supplier
const suppliers = useMemo(() => {
const map = new Map();
items.forEach((item) => {
if (!item.expand?.supplier) return;
const supplier = item.expand.supplier;
const coords = parseCoordinates(supplier.coordinates);
if (!coords) return;
const key = supplier.id;
if (!map.has(key)) {
map.set(key, { supplier, items: [], coords });
}
map.get(key)!.items.push(item);
});
return Array.from(map.values());
}, [items]);
const defaultCenter: [number, number] = [-33.9249, 18.4241];
const center = userLocation || defaultCenter;
const getDistance = (coords: { latitude: number; longitude: number }) => {
if (!userLocation) return null;
return calculateDistance(userLocation[0], userLocation[1], coords.latitude, coords.longitude);
};
return (
{userLocation && (
Your Location
)}
{/* Trail polyline */}
{showTrail && trail.length > 1 && (
<>
',
iconSize: [14, 14],
iconAnchor: [7, 7],
})}
>
Trip Start
>
)}
{suppliers.map(({ supplier, items: supplierItems, coords }) => {
const distance = getDistance(coords);
return (
{supplier.suppliername}
{supplier.supplieraddress && (
{supplier.supplieraddress}
)}
{distance && (
{distance < 1 ? `${Math.round(distance * 1000)}m` : `${distance.toFixed(1)} km`} away
)}
{supplierItems.map((i) => (
{(i.item_status === 'problem' || i.problem_flag) &&
}
{i.description}
Qty: {i.order_qty || i.quantity || 0}
))}
);
})}
{/* Floating controls */}
{/* Recenter */}
{userLocation && (
)}
{/* Trail toggle */}
{trail.length > 1 && (
)}
{/* Legend */}
Legend
{trail.length > 1 && (
)}
);
}