Sync property list with map viewport

Major changes:
- Property list now updates when map pans/zooms
- Properties sorted by distance from map center (closest first)
- Shows "X properties in view" when viewport filtering active
- Min 30 properties required before grouping kicks in
- Added rule to CLAUDE.md: no commits unless asked

Backend:
- MLS_Query: Added bounds filtering and distance-based sorting
- AJAX handler: Accepts bounds/center, sorts by distance when provided

Frontend:
- Map move triggers property list refresh with same viewport
- Loop prevention flag to avoid map->filter->map recursion
- Resets to page 1 when viewport changes

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Hanson.xyz Dev
2025-12-16 01:27:44 -06:00
parent 9337a3cbc7
commit acd606bb03
6 changed files with 151 additions and 37 deletions
File diff suppressed because one or more lines are too long
@@ -34,6 +34,19 @@ function homeproz_ajax_filter_properties() {
$sort = isset($_POST['sort']) ? sanitize_text_field($_POST['sort']) : 'newest';
$paged = isset($_POST['paged']) ? intval($_POST['paged']) : 1;
// Map bounds and center (for map-synced list view)
$bounds = null;
$center = null;
$has_map_bounds = false;
if (isset($_POST['bounds']) && is_array($_POST['bounds']) && count($_POST['bounds']) === 4) {
$bounds = array_map('floatval', $_POST['bounds']);
$has_map_bounds = true;
}
if (isset($_POST['center']) && is_array($_POST['center']) && count($_POST['center']) === 2) {
$center = array_map('floatval', $_POST['center']);
}
// Build filter args for count and properties
$per_page = 12;
$filter_args = array(
@@ -55,6 +68,9 @@ function homeproz_ajax_filter_properties() {
if ($beds) {
$filter_args['min_beds'] = $beds;
}
if ($bounds) {
$filter_args['bounds'] = $bounds;
}
// Get total count efficiently from database
$total = mls_get_property_count($filter_args);
@@ -64,10 +80,16 @@ function homeproz_ajax_filter_properties() {
$mls_args = array_merge($filter_args, array(
'limit' => $per_page,
'offset' => ($paged - 1) * $per_page,
'orderby' => 'modification_timestamp',
'order' => 'DESC',
));
// If we have map center, sort by distance; otherwise by date
if ($center) {
$mls_args['center'] = $center;
} else {
$mls_args['orderby'] = 'modification_timestamp';
$mls_args['order'] = 'DESC';
}
// Fetch only the properties we need for this page
$paged_properties = mls_get_properties($mls_args);
@@ -80,8 +102,11 @@ function homeproz_ajax_filter_properties() {
<?php if ($total > 0) : ?>
Showing <strong><?php echo esc_html($total); ?></strong>
<?php echo $total === 1 ? 'property' : 'properties'; ?>
<?php if ($has_map_bounds) : ?>
in view
<?php endif; ?>
<?php else : ?>
No properties found
No properties in view
<?php endif; ?>
</p>
</div>
@@ -113,19 +113,24 @@
var self = this;
var bounds = this.map.getBounds();
var center = this.map.getCenter();
var zoom = this.map.getZoom();
this.isLoading = true;
// Bounds array for both map clusters and property list
var boundsArray = [
bounds.getSouthWest().lat,
bounds.getSouthWest().lng,
bounds.getNorthEast().lat,
bounds.getNorthEast().lng
];
var centerArray = [center.lat, center.lng];
var requestData = {
action: 'mls_get_clusters',
zoom: zoom,
bounds: [
bounds.getSouthWest().lat,
bounds.getSouthWest().lng,
bounds.getNorthEast().lat,
bounds.getNorthEast().lng
],
bounds: boundsArray,
status: this.currentFilters.status || 'Active',
property_type: this.currentFilters.property_type || '',
city: this.currentFilters.city || '',
@@ -134,6 +139,9 @@
min_beds: this.currentFilters.min_beds || ''
};
// Also update the property list with the same viewport
PropertyFilters.updateFromMap(boundsArray, centerArray);
$.ajax({
url: homeprozMapData.clusterEndpoint,
type: 'GET',
@@ -518,6 +526,9 @@
// State
isFirstLoad: true,
isLoading: false,
mapBounds: null, // Current map viewport bounds
mapCenter: null, // Current map center for distance sorting
isMapUpdate: false, // Flag to prevent map->filter->map loop
/**
* Initialize
@@ -597,6 +608,18 @@
}
},
/**
* Update property list based on map viewport
* Called by PropertyMap when map moves/zooms
*/
updateFromMap: function(bounds, center) {
this.mapBounds = bounds;
this.mapCenter = center;
this.isMapUpdate = true;
// Reset to page 1 when map viewport changes
this.filterProperties(1, false);
},
/**
* Get page number from URL hash
*/
@@ -629,35 +652,47 @@
this.$results.html('<div class="property-results-loading"><div class="spinner"></div></div>');
}
// Build request data
var requestData = {
action: 'homeproz_filter_properties',
nonce: homeprozAjax.nonce,
property_type: formData.property_type,
property_location: formData.property_location,
min_price: formData.min_price,
max_price: formData.max_price,
beds: formData.beds,
paged: page
};
// Add map bounds and center if available
if (this.mapBounds) {
requestData.bounds = this.mapBounds;
}
if (this.mapCenter) {
requestData.center = this.mapCenter;
}
$.ajax({
url: homeprozAjax.ajaxUrl,
type: 'POST',
data: {
action: 'homeproz_filter_properties',
nonce: homeprozAjax.nonce,
property_type: formData.property_type,
property_location: formData.property_location,
min_price: formData.min_price,
max_price: formData.max_price,
beds: formData.beds,
paged: page
},
data: requestData,
success: function(response) {
if (response.success) {
self.$results.html(response.data.html);
self.isFirstLoad = false;
// Update map with new filter params
if (response.data.filters) {
// Update map with new filter params (but not if this was triggered by map move)
if (response.data.filters && !self.isMapUpdate) {
PropertyMap.updateFilters(response.data.filters);
}
self.isMapUpdate = false;
// Recalculate layout after content update
if (typeof LayoutCalculator !== 'undefined') {
LayoutCalculator.calculate();
}
// Update URL
// Update URL (skip when map-triggered)
if (updateHistory) {
self.updateUrl(formData, page);
}