();
for (const p of points) {
const group = byUtility.get(p.utilityType) ?? [];
group.push(p);
byUtility.set(p.utilityType, group);
}
for (const [utility, group] of byUtility) {
const sorted = [...group].sort((a, b) => orderKey(a) - orderKey(b));
const color = colorFor(utility);
const line = new google.maps.Polyline({
path: sorted.map((p) => ({ lat: p.lat, lng: p.lng })),
strokeColor: color,
strokeOpacity: 0.8,
strokeWeight: 3,
map,
});
overlaysRef.current.push(line);
for (const p of sorted) {
const marker = new google.maps.Marker({
position: { lat: p.lat, lng: p.lng },
map,
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: 5,
fillColor: color,
fillOpacity: 1,
strokeColor: '#ffffff',
strokeWeight: 1.5,
},
title: `${pointSummary(p)}\n${new Date(p.recordedAt).toLocaleString()}`,
});
overlaysRef.current.push(marker);
}
}
if (fitBounds && points.length !== fittedCountRef.current) {
fittedCountRef.current = points.length;
const bounds = new google.maps.LatLngBounds();
points.forEach((p) => bounds.extend({ lat: p.lat, lng: p.lng }));
map.fitBounds(bounds, 48);
}
}, [map, points, fitBounds]);
return null;
}
export default function GoogleJobMap({ points, fitBounds = true, heightPx = 480 }: JobMapProps) {
if (!API_KEY) {
return (
Set NEXT_PUBLIC_GOOGLE_MAPS_API_KEY in .env to enable the map.
);
}
return (
);
}