diff --git a/components/CollectionMap.tsx b/components/CollectionMap.tsx index 28c2bdd..d6ae32d 100644 --- a/components/CollectionMap.tsx +++ b/components/CollectionMap.tsx @@ -1,10 +1,11 @@ 'use client'; -import { useEffect, useMemo, useState } from 'react'; -import { MapContainer, TileLayer, Marker, Popup, useMap } from 'react-leaflet'; -import { OrderItem, Supplier } from '@/lib/types'; +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 { Navigation, AlertTriangle } from 'lucide-react'; +import { getDriverTrail, isAuthenticated, getCurrentUser } from '@/lib/pocketbase'; +import { Navigation, AlertTriangle, Route } from 'lucide-react'; import L from 'leaflet'; import 'leaflet/dist/leaflet.css'; @@ -32,7 +33,6 @@ interface MapViewProps { function MapBounds({ items }: { items: OrderItem[] }) { const map = useMap(); - useEffect(() => { const validPoints = items .map((i) => { @@ -40,19 +40,32 @@ function MapBounds({ items }: { items: OrderItem[] }) { 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: [50, 50], maxZoom: 15 }); } }, [map, items]); + return null; +} +function FitTrail({ trail }: { trail: [number, number][] }) { + const map = useMap(); + useEffect(() => { + if (trail.length > 1) { + const bounds = L.latLngBounds(trail); + map.fitBounds(bounds, { padding: [50, 50], maxZoom: 16 }); + } + }, [map, trail]); return null; } 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( @@ -63,22 +76,42 @@ export default function CollectionMap({ items, onNavigate }: MapViewProps) { } }, []); + // 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); // refresh every minute + 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]); @@ -93,7 +126,11 @@ export default function CollectionMap({ items, onNavigate }: MapViewProps) { return (
- + + {userLocation && ( @@ -102,10 +139,35 @@ export default function CollectionMap({ items, onNavigate }: MapViewProps) { )} + {/* Trail polyline */} + {showTrail && trail.length > 1 && ( + <> + + {/* Start marker */} +
', + iconSize: [14, 14], + iconAnchor: [7, 7], + })} + > +
Trip Start
+ + + )} + {suppliers.map(({ supplier, items: supplierItems, coords }) => { const distance = getDistance(coords); - const hasProblem = supplierItems.some((i) => i.item_status === 'problem' || i.problem_flag); - return ( @@ -113,7 +175,6 @@ export default function CollectionMap({ items, onNavigate }: MapViewProps) {

{supplier.suppliername}

{supplier.supplieraddress}

{distance &&

{distance.toFixed(1)} km away

} -
{supplierItems.map((i) => (
@@ -125,7 +186,6 @@ export default function CollectionMap({ items, onNavigate }: MapViewProps) {
))}
- + )} + + {/* Legend */} +
+

Legend

+
+
+
+ You
- Collection Stop +
+
+
+
+ Stop +
+ {trail.length > 1 && ( +
+
+ Route +
+ )}
diff --git a/lib/pocketbase.ts b/lib/pocketbase.ts index 236044c..1fa4ff3 100644 --- a/lib/pocketbase.ts +++ b/lib/pocketbase.ts @@ -70,6 +70,21 @@ export const saveDriverLocation = async (location: Omit) = } }; +// Get driver's location trail for today (for map polyline) +export const getDriverTrail = async (driverId: string) => { + try { + const today = new Date(); + today.setHours(0, 0, 0, 0); + const records = await pb.collection('driver_locations').getFullList({ + filter: `driver = "${driverId}" && timestamp >= "${today.toISOString()}"`, + sort: 'timestamp', + }); + return records as unknown as DriverLocation[]; + } catch { + return []; + } +}; + // Settings helpers const SETTINGS_KEY = '1driver_settings';