Grouping markers and changing pin to tear shape
This commit is contained in:
@@ -21,12 +21,6 @@ const ItemMarkerInfo: React.FC<ItemMarkerInfoProps> = ({ item, onViewDetails })
|
||||
return 'Contact for pricing';
|
||||
};
|
||||
|
||||
const getLocationDisplay = () => {
|
||||
return item.city && item.state
|
||||
? `${item.city}, ${item.state}`
|
||||
: 'Location not specified';
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ width: 'min(280px, 90vw)', maxWidth: '280px' }}>
|
||||
<div className="card border-0">
|
||||
@@ -55,18 +49,14 @@ const ItemMarkerInfo: React.FC<ItemMarkerInfoProps> = ({ item, onViewDetails })
|
||||
)}
|
||||
|
||||
<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}
|
||||
</h6>
|
||||
|
||||
<div className="mb-2">
|
||||
<span className="text-primary fw-bold">
|
||||
{getPriceDisplay()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="text-muted small mb-2">
|
||||
<i className="bi bi-geo-alt"></i> {getLocationDisplay()}
|
||||
<div className="mb-2">
|
||||
<span className="text-primary fw-semibold" style={{ fontSize: '0.8rem' }}>
|
||||
{getPriceDisplay()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{item.description && (
|
||||
|
||||
@@ -59,53 +59,59 @@ const SearchResultsMap: React.FC<SearchResultsMapProps> = ({
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 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<string, Item[]> = 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<SearchResultsMapProps> = ({
|
||||
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(
|
||||
<ItemMarkerInfo
|
||||
item={item}
|
||||
onViewDetails={() => {
|
||||
infoWindow.close();
|
||||
if (onItemSelect) {
|
||||
onItemSelect(item);
|
||||
} else {
|
||||
window.location.href = `/items/${item.id}`;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
if (itemCount === 1) {
|
||||
// Single item - show normal info
|
||||
root.render(
|
||||
<ItemMarkerInfo
|
||||
item={items[0]}
|
||||
onViewDetails={() => {
|
||||
infoWindow.close();
|
||||
if (onItemSelect) {
|
||||
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.open(map, marker);
|
||||
@@ -150,23 +179,22 @@ const SearchResultsMap: React.FC<SearchResultsMapProps> = ({
|
||||
[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
|
||||
|
||||
Reference in New Issue
Block a user