Fix city/zip filtering and improve map hover behavior
MLS Query Changes: - Use exact city/postal_code matching instead of radius search - Fixes city filter returning 1700+ results instead of 97 for Ramsey Cluster Endpoint: - Parse "City, SS" format to extract city name before querying - Fixes pins not showing when city filter applied Property Filters JS: - Always fit map bounds when filter changes (not just on no intersection) - Fit bounds on initial page load when URL has filters - Show temporary hover pin when marker is clustered or outside viewport - Uses markerCluster.getVisibleParent() to detect clustered markers Property Results: - Add zip code parameter handling for URL filters 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -209,40 +209,13 @@ class MLS_Query {
|
||||
|
||||
// City and postal_code are mutually exclusive - city takes priority
|
||||
if ($args['city']) {
|
||||
// Look up city coordinates for radius search
|
||||
$city_coords = $this->get_city_coordinates($args['city']);
|
||||
if ($city_coords) {
|
||||
// Match exact city OR within 15 miles of city center
|
||||
$distance_filter = $this->get_distance_filter_sql(
|
||||
$city_coords['latitude'],
|
||||
$city_coords['longitude'],
|
||||
15 // miles
|
||||
);
|
||||
$where[] = "(city = %s OR ({$distance_filter}))";
|
||||
$values[] = $args['city'];
|
||||
} else {
|
||||
// Fallback to exact match if city not in geo table
|
||||
// Exact city match
|
||||
$where[] = 'city = %s';
|
||||
$values[] = $args['city'];
|
||||
}
|
||||
} elseif ($args['postal_code']) {
|
||||
// Only apply postal_code filter if city is not set
|
||||
// Look up zip code coordinates for radius search
|
||||
$zip_coords = $this->get_zipcode_coordinates($args['postal_code']);
|
||||
if ($zip_coords) {
|
||||
// Match exact zip code OR within 20 miles of zip code center
|
||||
$distance_filter = $this->get_distance_filter_sql(
|
||||
$zip_coords['latitude'],
|
||||
$zip_coords['longitude'],
|
||||
20 // miles
|
||||
);
|
||||
$where[] = "(postal_code = %s OR ({$distance_filter}))";
|
||||
$values[] = $args['postal_code'];
|
||||
} else {
|
||||
// Fallback to exact match if zip code not in geo table
|
||||
// Exact postal code match
|
||||
$where[] = 'postal_code = %s';
|
||||
$values[] = $args['postal_code'];
|
||||
}
|
||||
} elseif ($args['center_lat'] && $args['center_lng']) {
|
||||
// Direct lat/lng radius search (from homepage location search)
|
||||
$distance_filter = $this->get_distance_filter_sql(
|
||||
@@ -548,40 +521,13 @@ class MLS_Query {
|
||||
|
||||
// City and postal_code are mutually exclusive - city takes priority
|
||||
if (!empty($args['city'])) {
|
||||
// Look up city coordinates for radius search
|
||||
$city_coords = $this->get_city_coordinates($args['city']);
|
||||
if ($city_coords) {
|
||||
// Match exact city OR within 15 miles of city center
|
||||
$distance_filter = $this->get_distance_filter_sql(
|
||||
$city_coords['latitude'],
|
||||
$city_coords['longitude'],
|
||||
15 // miles
|
||||
);
|
||||
$where[] = "(city = %s OR ({$distance_filter}))";
|
||||
$values[] = $args['city'];
|
||||
} else {
|
||||
// Fallback to exact match if city not in geo table
|
||||
// Exact city match
|
||||
$where[] = 'city = %s';
|
||||
$values[] = $args['city'];
|
||||
}
|
||||
} elseif (!empty($args['postal_code'])) {
|
||||
// Only apply postal_code filter if city is not set
|
||||
// Look up zip code coordinates for radius search
|
||||
$zip_coords = $this->get_zipcode_coordinates($args['postal_code']);
|
||||
if ($zip_coords) {
|
||||
// Match exact zip code OR within 20 miles of zip code center
|
||||
$distance_filter = $this->get_distance_filter_sql(
|
||||
$zip_coords['latitude'],
|
||||
$zip_coords['longitude'],
|
||||
20 // miles
|
||||
);
|
||||
$where[] = "(postal_code = %s OR ({$distance_filter}))";
|
||||
$values[] = $args['postal_code'];
|
||||
} else {
|
||||
// Fallback to exact match if zip code not in geo table
|
||||
// Exact postal code match
|
||||
$where[] = 'postal_code = %s';
|
||||
$values[] = $args['postal_code'];
|
||||
}
|
||||
} elseif (!empty($args['center_lat']) && !empty($args['center_lng'])) {
|
||||
// Direct lat/lng radius search (from homepage location search)
|
||||
$radius = !empty($args['radius']) ? (int) $args['radius'] : 30;
|
||||
@@ -806,41 +752,14 @@ class MLS_Query {
|
||||
|
||||
// City and postal_code are mutually exclusive - city takes priority
|
||||
if (!empty($args['city'])) {
|
||||
// Look up city coordinates for radius search
|
||||
$city_coords = $this->get_city_coordinates($args['city']);
|
||||
if ($city_coords) {
|
||||
// Match exact city OR within 15 miles of city center
|
||||
$distance_filter = $this->get_distance_filter_sql(
|
||||
$city_coords['latitude'],
|
||||
$city_coords['longitude'],
|
||||
15 // miles
|
||||
);
|
||||
$where[] = "(city = %s OR ({$distance_filter}))";
|
||||
$values[] = $args['city'];
|
||||
} else {
|
||||
// Fallback to exact match if city not in geo table
|
||||
// Exact city match
|
||||
$where[] = 'city = %s';
|
||||
$values[] = $args['city'];
|
||||
}
|
||||
} elseif (!empty($args['postal_code'])) {
|
||||
// Only apply postal_code filter if city is not set
|
||||
// Look up zip code coordinates for radius search
|
||||
$zip_coords = $this->get_zipcode_coordinates($args['postal_code']);
|
||||
if ($zip_coords) {
|
||||
// Match exact zip code OR within 20 miles of zip code center
|
||||
$distance_filter = $this->get_distance_filter_sql(
|
||||
$zip_coords['latitude'],
|
||||
$zip_coords['longitude'],
|
||||
20 // miles
|
||||
);
|
||||
$where[] = "(postal_code = %s OR ({$distance_filter}))";
|
||||
$values[] = $args['postal_code'];
|
||||
} else {
|
||||
// Fallback to exact match if zip code not in geo table
|
||||
// Exact postal code match
|
||||
$where[] = 'postal_code = %s';
|
||||
$values[] = $args['postal_code'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($args['min_price'])) {
|
||||
$where[] = 'list_price >= %d';
|
||||
|
||||
Regular → Executable
+4
@@ -256,6 +256,10 @@ final class MLS_Plugin {
|
||||
$status = isset($_REQUEST['status']) ? sanitize_text_field($_REQUEST['status']) : 'Active';
|
||||
$property_type = isset($_REQUEST['property_type']) ? sanitize_text_field($_REQUEST['property_type']) : null;
|
||||
$city = isset($_REQUEST['city']) ? sanitize_text_field($_REQUEST['city']) : null;
|
||||
// Parse "City, SS" format - extract just the city name
|
||||
if ($city && preg_match('/^(.+),\s*([A-Z]{2})$/', $city, $matches)) {
|
||||
$city = $matches[1];
|
||||
}
|
||||
$min_price = isset($_REQUEST['min_price']) ? (int) $_REQUEST['min_price'] : null;
|
||||
$max_price = isset($_REQUEST['max_price']) ? (int) $_REQUEST['max_price'] : null;
|
||||
$min_beds = isset($_REQUEST['min_beds']) ? (int) $_REQUEST['min_beds'] : null;
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -840,13 +840,36 @@
|
||||
|
||||
self.hoveredPropertyId = propertyId;
|
||||
|
||||
// Check if marker exists on map
|
||||
if (self.markers[propertyId]) {
|
||||
// Marker exists - highlight it
|
||||
var marker = self.markers[propertyId];
|
||||
var needsTemporaryMarker = false;
|
||||
|
||||
if (marker) {
|
||||
// Marker exists - check if it's visible (not clustered and in viewport)
|
||||
var markerLatLng = marker.getLatLng();
|
||||
var inViewport = self.map.getBounds().contains(markerLatLng);
|
||||
|
||||
// Check if marker is clustered (part of a cluster group)
|
||||
var isClustered = false;
|
||||
if (self.markerCluster && self.markerCluster.hasLayer(marker)) {
|
||||
var visibleParent = self.markerCluster.getVisibleParent(marker);
|
||||
isClustered = visibleParent && visibleParent !== marker;
|
||||
}
|
||||
|
||||
if (!isClustered && inViewport) {
|
||||
// Marker is visible as individual pin - highlight it
|
||||
self.setMarkerColor(propertyId, 'blue');
|
||||
self.setMarkerZIndex(propertyId, 9000); // Blue below amber but above red
|
||||
self.setMarkerZIndex(propertyId, 9000);
|
||||
} else {
|
||||
// Marker is clustered - create temporary pin at property location
|
||||
// Marker is clustered or outside viewport - need temporary marker
|
||||
needsTemporaryMarker = true;
|
||||
}
|
||||
} else {
|
||||
// Marker doesn't exist in current dataset - need temporary marker
|
||||
needsTemporaryMarker = true;
|
||||
}
|
||||
|
||||
// Create temporary marker if needed
|
||||
if (needsTemporaryMarker) {
|
||||
var lat = $card.data('lat');
|
||||
var lng = $card.data('lng');
|
||||
|
||||
@@ -981,7 +1004,7 @@
|
||||
});
|
||||
|
||||
// City/Zip mutual exclusivity in sticky form - city clears zip
|
||||
this.$stickyForm.find('select[name="property_location"]').on('change', function() {
|
||||
this.$stickyForm.find('select[name="city"]').on('change', function() {
|
||||
if ($(this).val()) {
|
||||
self.$stickyForm.find('input[name="zip"]').val('');
|
||||
self.$mainForm.find('input[name="zip"]').val('');
|
||||
@@ -991,8 +1014,8 @@
|
||||
// City/Zip mutual exclusivity in sticky form - zip clears city
|
||||
this.$stickyForm.find('input[name="zip"]').on('input', function() {
|
||||
if ($(this).val()) {
|
||||
self.$stickyForm.find('select[name="property_location"]').val('');
|
||||
self.$mainForm.find('select[name="property_location"]').val('');
|
||||
self.$stickyForm.find('select[name="city"]').val('');
|
||||
self.$mainForm.find('select[name="city"]').val('');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1118,7 +1141,7 @@
|
||||
});
|
||||
|
||||
// City/Zip mutual exclusivity - city clears zip
|
||||
this.$form.find('select[name="property_location"]').on('change', function() {
|
||||
this.$form.find('select[name="city"]').on('change', function() {
|
||||
if ($(this).val()) {
|
||||
self.$form.find('input[name="zip"]').val('');
|
||||
// Also sync to sticky form if it exists
|
||||
@@ -1131,10 +1154,10 @@
|
||||
// City/Zip mutual exclusivity - zip clears city
|
||||
this.$form.find('input[name="zip"]').on('input', function() {
|
||||
if ($(this).val()) {
|
||||
self.$form.find('select[name="property_location"]').val('');
|
||||
self.$form.find('select[name="city"]').val('');
|
||||
// Also sync to sticky form if it exists
|
||||
if (StickyFilters && StickyFilters.$stickyForm) {
|
||||
StickyFilters.$stickyForm.find('select[name="property_location"]').val('');
|
||||
StickyFilters.$stickyForm.find('select[name="city"]').val('');
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1220,7 +1243,7 @@
|
||||
PropertyMap.currentFilters = {
|
||||
status: 'Active',
|
||||
property_type: formData.property_type || '',
|
||||
city: formData.property_location || '',
|
||||
city: formData.city || '',
|
||||
min_price: formData.min_price || '',
|
||||
max_price: formData.max_price || '',
|
||||
min_beds: formData.beds || ''
|
||||
@@ -1291,7 +1314,7 @@
|
||||
action: 'homeproz_filter_properties',
|
||||
nonce: homeprozAjax.nonce,
|
||||
property_type: formData.property_type,
|
||||
property_location: formData.property_location,
|
||||
city: formData.city,
|
||||
zip: formData.zip,
|
||||
min_price: formData.min_price,
|
||||
max_price: formData.max_price,
|
||||
@@ -1529,7 +1552,7 @@
|
||||
action: 'homeproz_filter_properties',
|
||||
nonce: homeprozAjax.nonce,
|
||||
property_type: formData.property_type,
|
||||
property_location: formData.property_location,
|
||||
city: formData.city,
|
||||
zip: formData.zip,
|
||||
min_price: formData.min_price,
|
||||
max_price: formData.max_price,
|
||||
@@ -1628,7 +1651,7 @@
|
||||
getFormData: function() {
|
||||
return {
|
||||
property_type: this.$form.find('[name="property_type"]').val() || '',
|
||||
property_location: this.$form.find('[name="property_location"]').val() || '',
|
||||
city: this.$form.find('[name="city"]').val() || '',
|
||||
zip: this.$form.find('[name="zip"]').val() || '',
|
||||
min_price: this.$form.find('[name="min_price"]').val() || '',
|
||||
max_price: this.$form.find('[name="max_price"]').val() || '',
|
||||
@@ -1729,7 +1752,7 @@
|
||||
return {
|
||||
// Filters
|
||||
property_type: raw.property_type || '',
|
||||
property_location: raw.property_location || '',
|
||||
city: raw.city || '',
|
||||
zip: raw.zip || '',
|
||||
min_price: raw.min_price || '',
|
||||
max_price: raw.max_price || '',
|
||||
@@ -1792,7 +1815,7 @@
|
||||
PropertyMap.currentFilters = {
|
||||
status: 'Active',
|
||||
property_type: formData.property_type || '',
|
||||
city: formData.property_location || '',
|
||||
city: formData.city || '',
|
||||
min_price: formData.min_price || '',
|
||||
max_price: formData.max_price || '',
|
||||
min_beds: formData.beds || ''
|
||||
@@ -1812,7 +1835,7 @@
|
||||
data: {
|
||||
action: 'homeproz_get_filter_bounds',
|
||||
property_type: formData.property_type,
|
||||
city: formData.property_location,
|
||||
city: formData.city,
|
||||
min_price: formData.min_price,
|
||||
max_price: formData.max_price,
|
||||
min_beds: formData.beds
|
||||
@@ -1820,19 +1843,7 @@
|
||||
success: function(response) {
|
||||
if (response.success && response.data) {
|
||||
var filterBounds = response.data;
|
||||
var mapBounds = PropertyMap.map.getBounds();
|
||||
|
||||
// Check if map view intersects with filter bounds
|
||||
var filterLatLngBounds = L.latLngBounds(
|
||||
[filterBounds.sw_lat, filterBounds.sw_lng],
|
||||
[filterBounds.ne_lat, filterBounds.ne_lng]
|
||||
);
|
||||
|
||||
// Check if ANY of the filter bounds corners are visible in the map
|
||||
// OR if the map view is fully contained within the filter bounds
|
||||
var mapIntersects = mapBounds.intersects(filterLatLngBounds);
|
||||
|
||||
if (!mapIntersects) {
|
||||
// Add 10% padding to bounds
|
||||
var latPadding = (filterBounds.ne_lat - filterBounds.sw_lat) * 0.1;
|
||||
var lngPadding = (filterBounds.ne_lng - filterBounds.sw_lng) * 0.1;
|
||||
@@ -1842,13 +1853,9 @@
|
||||
[filterBounds.ne_lat + latPadding, filterBounds.ne_lng + lngPadding]
|
||||
);
|
||||
|
||||
// Reposition map to show filtered properties
|
||||
// Always fit map to show all filtered properties
|
||||
PropertyMap.map.fitBounds(paddedBounds);
|
||||
// The moveend event will trigger loadClusters and updateFromMap
|
||||
} else {
|
||||
// Map already shows relevant area, just reload clusters and properties
|
||||
PropertyMap.loadClusters();
|
||||
}
|
||||
} else {
|
||||
// No properties found with these filters, just reload
|
||||
PropertyMap.loadClusters();
|
||||
@@ -1890,7 +1897,7 @@
|
||||
var mapFilters = {
|
||||
status: 'Active',
|
||||
property_type: formData.property_type || '',
|
||||
city: formData.property_location || '',
|
||||
city: formData.city || '',
|
||||
min_price: formData.min_price || '',
|
||||
max_price: formData.max_price || '',
|
||||
min_beds: formData.beds || ''
|
||||
@@ -1901,6 +1908,9 @@
|
||||
// Restore state if we have pending state to restore
|
||||
if (PropertyFilters.pendingRestoreState) {
|
||||
PropertyFilters.restoreState();
|
||||
} else if (formData.city || formData.zip || formData.property_type || formData.min_price || formData.max_price || formData.beds) {
|
||||
// Filters present from URL but no saved map state - fit to filter bounds
|
||||
PropertyFilters.onFilterChange();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1940,7 +1950,7 @@
|
||||
var mapFilters = {
|
||||
status: 'Active',
|
||||
property_type: formData.property_type || '',
|
||||
city: formData.property_location || '',
|
||||
city: formData.city || '',
|
||||
min_price: formData.min_price || '',
|
||||
max_price: formData.max_price || '',
|
||||
min_beds: formData.beds || ''
|
||||
@@ -2480,7 +2490,7 @@
|
||||
action: 'homeproz_filter_properties',
|
||||
nonce: homeprozAjax.nonce,
|
||||
property_type: formData.property_type,
|
||||
property_location: formData.property_location,
|
||||
city: formData.city,
|
||||
zip: formData.zip,
|
||||
min_price: formData.min_price,
|
||||
max_price: formData.max_price,
|
||||
|
||||
@@ -26,7 +26,8 @@ if (!function_exists('mls_get_properties')) {
|
||||
// Get filter values from URL
|
||||
$current_type = isset($_GET['property_type']) ? sanitize_text_field($_GET['property_type']) : '';
|
||||
$current_status = isset($_GET['property_status']) ? sanitize_text_field($_GET['property_status']) : 'Active';
|
||||
$current_location = isset($_GET['property_location']) ? sanitize_text_field($_GET['property_location']) : '';
|
||||
$current_location = isset($_GET['city']) ? sanitize_text_field($_GET['city']) : '';
|
||||
$current_zip = isset($_GET['zip']) ? sanitize_text_field($_GET['zip']) : '';
|
||||
$current_min_price = isset($_GET['min_price']) ? intval($_GET['min_price']) : '';
|
||||
$current_max_price = isset($_GET['max_price']) ? intval($_GET['max_price']) : '';
|
||||
$current_beds = isset($_GET['beds']) ? intval($_GET['beds']) : '';
|
||||
@@ -49,7 +50,15 @@ if ($current_type) {
|
||||
$filter_args['property_type'] = $current_type;
|
||||
}
|
||||
if ($current_location) {
|
||||
$filter_args['city'] = $current_location;
|
||||
// Parse "City, SS" format - extract just the city name
|
||||
$city_name = $current_location;
|
||||
if (preg_match('/^(.+),\s*([A-Z]{2})$/', $current_location, $matches)) {
|
||||
$city_name = $matches[1];
|
||||
}
|
||||
$filter_args['city'] = $city_name;
|
||||
} elseif ($current_zip) {
|
||||
// Zip code filter (only if city not set)
|
||||
$filter_args['postal_code'] = $current_zip;
|
||||
}
|
||||
// Use lat/lng radius search if provided (takes precedence over location)
|
||||
if ($center_lat && $center_lng) {
|
||||
|
||||
Reference in New Issue
Block a user