diff --git a/frontend/src/components/ItemMarkerInfo.tsx b/frontend/src/components/ItemMarkerInfo.tsx index c690953..8ca0a66 100644 --- a/frontend/src/components/ItemMarkerInfo.tsx +++ b/frontend/src/components/ItemMarkerInfo.tsx @@ -21,12 +21,6 @@ const ItemMarkerInfo: React.FC = ({ item, onViewDetails }) return 'Contact for pricing'; }; - const getLocationDisplay = () => { - return item.city && item.state - ? `${item.city}, ${item.state}` - : 'Location not specified'; - }; - return (
@@ -55,18 +49,14 @@ const ItemMarkerInfo: React.FC = ({ item, onViewDetails }) )}
-
+
{item.name} -
- -
- - {getPriceDisplay()} -
-
- {getLocationDisplay()} +
+ + {getPriceDisplay()} +
{item.description && ( diff --git a/frontend/src/components/SearchResultsMap.tsx b/frontend/src/components/SearchResultsMap.tsx index 8c769f8..89c3442 100644 --- a/frontend/src/components/SearchResultsMap.tsx +++ b/frontend/src/components/SearchResultsMap.tsx @@ -59,53 +59,59 @@ const SearchResultsMap: React.FC = ({ } }, []); - // Create marker for an item - const createItemMarker = useCallback( - async (item: Item, map: google.maps.Map) => { - if (!item.latitude || !item.longitude) return null; + // Group items by location + const groupItemsByLocation = useCallback((items: Item[]) => { + const groups: Map = new Map(); - const { AdvancedMarkerElement } = (await google.maps.importLibrary( - "marker" - )) as google.maps.MarkerLibrary; + items.forEach((item) => { + if (item.latitude && item.longitude) { + const key = `${item.latitude},${item.longitude}`; + const existing = groups.get(key) || []; + existing.push(item); + groups.set(key, existing); + } + }); - // Create marker element + return groups; + }, []); + + // Create marker for a group of items at the same location + const createLocationMarker = useCallback( + async (items: Item[], map: google.maps.Map) => { + if (items.length === 0 || !items[0].latitude || !items[0].longitude) return null; + + const { AdvancedMarkerElement, PinElement } = + (await google.maps.importLibrary( + "marker" + )) as google.maps.MarkerLibrary; + + const itemCount = items.length; const isMobile = window.innerWidth < 768; - const markerSize = isMobile ? 48 : 40; - const markerElement = document.createElement("div"); - markerElement.className = - "d-flex align-items-center justify-content-center"; - markerElement.style.cssText = ` - width: ${markerSize}px; - height: ${markerSize}px; - background-color: #0d6efd; - border: 3px solid white; - border-radius: 50%; - cursor: pointer; - box-shadow: 0 2px 8px rgba(0,0,0,0.3); - transition: transform 0.2s ease; - touch-action: manipulation; - `; - - // Add icon - const icon = document.createElement("i"); - icon.className = "bi bi-geo-fill text-white"; - icon.style.fontSize = "16px"; - markerElement.appendChild(icon); - - // Hover effects - markerElement.addEventListener("mouseenter", () => { - markerElement.style.transform = "scale(1.1)"; + // Create teardrop pin marker with badge for multiple items + const pin = new PinElement({ + background: "#0d6efd", + borderColor: "#ffffff", + glyphColor: "#ffffff", + scale: isMobile ? 1.2 : 1.0, + glyph: itemCount > 1 ? `${itemCount}` : undefined, }); - markerElement.addEventListener("mouseleave", () => { - markerElement.style.transform = "scale(1)"; + + // Add hover effects + pin.element.style.cursor = "pointer"; + pin.element.style.transition = "transform 0.2s ease"; + pin.element.addEventListener("mouseenter", () => { + pin.element.style.transform = "scale(1.1)"; + }); + pin.element.addEventListener("mouseleave", () => { + pin.element.style.transform = "scale(1)"; }); const marker = new AdvancedMarkerElement({ - position: { lat: item.latitude, lng: item.longitude }, + position: { lat: items[0].latitude, lng: items[0].longitude }, map: map, - content: markerElement, - title: item.name, + content: pin.element, + title: itemCount > 1 ? `${itemCount} items` : items[0].name, }); // Add click listener @@ -118,27 +124,50 @@ const SearchResultsMap: React.FC = ({ headerDisabled: true, maxWidth: window.innerWidth < 768 - ? Math.min(300, window.innerWidth - 40) - : 300, + ? Math.min(320, window.innerWidth - 40) + : 320, }); // Create React container for info window content const infoContainer = document.createElement("div"); const root = createRoot(infoContainer); - root.render( - { - infoWindow.close(); - if (onItemSelect) { - onItemSelect(item); - } else { - window.location.href = `/items/${item.id}`; - } - }} - /> - ); + if (itemCount === 1) { + // Single item - show normal info + root.render( + { + infoWindow.close(); + if (onItemSelect) { + onItemSelect(items[0]); + } else { + window.location.href = `/items/${items[0].id}`; + } + }} + /> + ); + } else { + // Multiple items - show list + root.render( +
+ {items.map((item) => ( + { + infoWindow.close(); + if (onItemSelect) { + onItemSelect(item); + } else { + window.location.href = `/items/${item.id}`; + } + }} + /> + ))} +
+ ); + } infoWindow.setContent(infoContainer); infoWindow.open(map, marker); @@ -150,23 +179,22 @@ const SearchResultsMap: React.FC = ({ [onItemSelect] ); - // Add markers for all items + // Add markers for all items (grouped by location) const addMarkersToMap = useCallback( async (map: google.maps.Map, items: Item[]) => { clearMarkers(); - const validItems = items.filter( - (item) => item.latitude && item.longitude - ); + const locationGroups = groupItemsByLocation(items); - for (const item of validItems) { - const marker = await createItemMarker(item, map); + const groupsArray = Array.from(locationGroups.values()); + for (const groupItems of groupsArray) { + const marker = await createLocationMarker(groupItems, map); if (marker) { markersRef.current.push(marker); } } }, - [createItemMarker, clearMarkers] + [createLocationMarker, clearMarkers, groupItemsByLocation] ); // Calculate map bounds to fit all markers with appropriate zoom levels