Grouping markers and changing pin to tear shape

This commit is contained in:
jackiettran
2025-12-30 16:58:03 -05:00
parent 6cf8a009ff
commit 4bb4e7bcb6
2 changed files with 93 additions and 75 deletions

View File

@@ -21,12 +21,6 @@ const ItemMarkerInfo: React.FC<ItemMarkerInfoProps> = ({ item, onViewDetails })
return 'Contact for pricing'; return 'Contact for pricing';
}; };
const getLocationDisplay = () => {
return item.city && item.state
? `${item.city}, ${item.state}`
: 'Location not specified';
};
return ( return (
<div style={{ width: 'min(280px, 90vw)', maxWidth: '280px' }}> <div style={{ width: 'min(280px, 90vw)', maxWidth: '280px' }}>
<div className="card border-0"> <div className="card border-0">
@@ -55,18 +49,14 @@ const ItemMarkerInfo: React.FC<ItemMarkerInfoProps> = ({ item, onViewDetails })
)} )}
<div className="card-body p-3"> <div className="card-body p-3">
<h6 className="card-title mb-2 text-dark fw-bold text-truncate"> <div className="card-title mb-2 text-dark fw-bold text-truncate" style={{ fontSize: '0.875rem' }}>
{item.name} {item.name}
</h6>
<div className="mb-2">
<span className="text-primary fw-bold">
{getPriceDisplay()}
</span>
</div> </div>
<div className="text-muted small mb-2"> <div className="mb-2">
<i className="bi bi-geo-alt"></i> {getLocationDisplay()} <span className="text-primary fw-semibold" style={{ fontSize: '0.8rem' }}>
{getPriceDisplay()}
</span>
</div> </div>
{item.description && ( {item.description && (

View File

@@ -59,53 +59,59 @@ const SearchResultsMap: React.FC<SearchResultsMapProps> = ({
} }
}, []); }, []);
// Create marker for an item // Group items by location
const createItemMarker = useCallback( const groupItemsByLocation = useCallback((items: Item[]) => {
async (item: Item, map: google.maps.Map) => { const groups: Map<string, Item[]> = new Map();
if (!item.latitude || !item.longitude) return null;
const { AdvancedMarkerElement } = (await google.maps.importLibrary( items.forEach((item) => {
"marker" if (item.latitude && item.longitude) {
)) as google.maps.MarkerLibrary; 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 isMobile = window.innerWidth < 768;
const markerSize = isMobile ? 48 : 40;
const markerElement = document.createElement("div"); // Create teardrop pin marker with badge for multiple items
markerElement.className = const pin = new PinElement({
"d-flex align-items-center justify-content-center"; background: "#0d6efd",
markerElement.style.cssText = ` borderColor: "#ffffff",
width: ${markerSize}px; glyphColor: "#ffffff",
height: ${markerSize}px; scale: isMobile ? 1.2 : 1.0,
background-color: #0d6efd; glyph: itemCount > 1 ? `${itemCount}` : undefined,
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)";
}); });
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({ const marker = new AdvancedMarkerElement({
position: { lat: item.latitude, lng: item.longitude }, position: { lat: items[0].latitude, lng: items[0].longitude },
map: map, map: map,
content: markerElement, content: pin.element,
title: item.name, title: itemCount > 1 ? `${itemCount} items` : items[0].name,
}); });
// Add click listener // Add click listener
@@ -118,27 +124,50 @@ const SearchResultsMap: React.FC<SearchResultsMapProps> = ({
headerDisabled: true, headerDisabled: true,
maxWidth: maxWidth:
window.innerWidth < 768 window.innerWidth < 768
? Math.min(300, window.innerWidth - 40) ? Math.min(320, window.innerWidth - 40)
: 300, : 320,
}); });
// Create React container for info window content // Create React container for info window content
const infoContainer = document.createElement("div"); const infoContainer = document.createElement("div");
const root = createRoot(infoContainer); const root = createRoot(infoContainer);
root.render( if (itemCount === 1) {
<ItemMarkerInfo // Single item - show normal info
item={item} root.render(
onViewDetails={() => { <ItemMarkerInfo
infoWindow.close(); item={items[0]}
if (onItemSelect) { onViewDetails={() => {
onItemSelect(item); infoWindow.close();
} else { if (onItemSelect) {
window.location.href = `/items/${item.id}`; onItemSelect(items[0]);
} } else {
}} window.location.href = `/items/${items[0].id}`;
/> }
); }}
/>
);
} else {
// Multiple items - show list
root.render(
<div style={{ width: 'min(300px, 85vw)', maxHeight: '350px', overflowY: 'auto' }}>
{items.map((item) => (
<ItemMarkerInfo
key={item.id}
item={item}
onViewDetails={() => {
infoWindow.close();
if (onItemSelect) {
onItemSelect(item);
} else {
window.location.href = `/items/${item.id}`;
}
}}
/>
))}
</div>
);
}
infoWindow.setContent(infoContainer); infoWindow.setContent(infoContainer);
infoWindow.open(map, marker); infoWindow.open(map, marker);
@@ -150,23 +179,22 @@ const SearchResultsMap: React.FC<SearchResultsMapProps> = ({
[onItemSelect] [onItemSelect]
); );
// Add markers for all items // Add markers for all items (grouped by location)
const addMarkersToMap = useCallback( const addMarkersToMap = useCallback(
async (map: google.maps.Map, items: Item[]) => { async (map: google.maps.Map, items: Item[]) => {
clearMarkers(); clearMarkers();
const validItems = items.filter( const locationGroups = groupItemsByLocation(items);
(item) => item.latitude && item.longitude
);
for (const item of validItems) { const groupsArray = Array.from(locationGroups.values());
const marker = await createItemMarker(item, map); for (const groupItems of groupsArray) {
const marker = await createLocationMarker(groupItems, map);
if (marker) { if (marker) {
markersRef.current.push(marker); markersRef.current.push(marker);
} }
} }
}, },
[createItemMarker, clearMarkers] [createLocationMarker, clearMarkers, groupItemsByLocation]
); );
// Calculate map bounds to fit all markers with appropriate zoom levels // Calculate map bounds to fit all markers with appropriate zoom levels