Implement high priority features: filters, property type boxes, email routing
- Expanded search filters: MLS property types dropdown, MLS cities (50+ listings), zip code text input - Property type showcase boxes: 5-category grid on homepage with icons, descriptions, counts - Multi-recipient email: Primary to office@, CC to info@, sender confirmation receipt enabled - Added helper functions: homeproz_get_mls_property_types(), homeproz_get_mls_cities() - Updated FEATURES_PENDING with completion status 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
{"id":"html-117","title":"Filter MLS queries to MN and IA only","description":"","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-16T11:53:08.470255883-06:00","updated_at":"2025-12-16T12:45:04.808441903-06:00","closed_at":"2025-12-16T12:45:04.808441903-06:00","close_reason":"State filter implemented for all MLS query methods (get_properties, get_distinct_cities, get_distinct_counties, get_count, has_data, get_property_types, get_price_range). Added MLS_ALLOWED_STATES constant."}
|
{"id":"html-117","title":"Filter MLS queries to MN and IA only","description":"","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-16T11:53:08.470255883-06:00","updated_at":"2025-12-16T12:45:04.808441903-06:00","closed_at":"2025-12-16T12:45:04.808441903-06:00","close_reason":"State filter implemented for all MLS query methods (get_properties, get_distinct_cities, get_distinct_counties, get_count, has_data, get_property_types, get_price_range). Added MLS_ALLOWED_STATES constant."}
|
||||||
{"id":"html-2fg","title":"Update office hours in footer","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-16T11:53:03.244743372-06:00","updated_at":"2025-12-16T11:54:52.108263115-06:00","closed_at":"2025-12-16T11:54:52.108263115-06:00","close_reason":"Updated office hours to Mon-Fri 9am-4pm, Sat/Sun By Appointment"}
|
{"id":"html-2fg","title":"Update office hours in footer","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-16T11:53:03.244743372-06:00","updated_at":"2025-12-16T11:54:52.108263115-06:00","closed_at":"2025-12-16T11:54:52.108263115-06:00","close_reason":"Updated office hours to Mon-Fri 9am-4pm, Sat/Sun By Appointment"}
|
||||||
{"id":"html-2fp","title":"Separate Residential and Commercial listings on homepage","description":"","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-30T02:24:09.984594683-06:00","updated_at":"2025-11-30T02:33:12.32537052-06:00","closed_at":"2025-11-30T02:33:12.32537052-06:00","close_reason":"Separated Featured Homes and Commercial/Land into distinct homepage sections"}
|
{"id":"html-2fp","title":"Separate Residential and Commercial listings on homepage","description":"","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-30T02:24:09.984594683-06:00","updated_at":"2025-11-30T02:33:12.32537052-06:00","closed_at":"2025-11-30T02:33:12.32537052-06:00","close_reason":"Separated Featured Homes and Commercial/Land into distinct homepage sections"}
|
||||||
|
{"id":"html-2kf","title":"Multi-recipient email routing for contact form","description":"","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-16T13:11:50.988722809-06:00","updated_at":"2025-12-16T13:19:40.448106043-06:00","closed_at":"2025-12-16T13:19:40.448106043-06:00","close_reason":"Configured multi-recipient email: Primary mail to office@ with CC to info@. Enabled Mail 2 confirmation receipt to sender. For agent-specific routing, office staff forwards as needed."}
|
||||||
{"id":"html-3fb","title":"MLS by HansonXyz Plugin - Phase 5: Public API","description":"Query class with filter support, global helper functions (mls_get_properties, etc), integration hooks for themes","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-14T21:02:45.05131653-06:00","updated_at":"2025-12-14T21:21:46.940975819-06:00","closed_at":"2025-12-14T21:21:46.940975819-06:00","dependencies":[{"issue_id":"html-3fb","depends_on_id":"html-5j7","type":"blocks","created_at":"2025-12-14T21:04:05.308661828-06:00","created_by":"unknown"}]}
|
{"id":"html-3fb","title":"MLS by HansonXyz Plugin - Phase 5: Public API","description":"Query class with filter support, global helper functions (mls_get_properties, etc), integration hooks for themes","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-14T21:02:45.05131653-06:00","updated_at":"2025-12-14T21:21:46.940975819-06:00","closed_at":"2025-12-14T21:21:46.940975819-06:00","dependencies":[{"issue_id":"html-3fb","depends_on_id":"html-5j7","type":"blocks","created_at":"2025-12-14T21:04:05.308661828-06:00","created_by":"unknown"}]}
|
||||||
{"id":"html-3nq","title":"Enhance footer with office hours, professional logos, license numbers","description":"","status":"closed","priority":3,"issue_type":"task","created_at":"2025-11-30T02:24:30.889106857-06:00","updated_at":"2025-11-30T02:46:52.35661921-06:00","closed_at":"2025-11-30T02:46:52.35661921-06:00","close_reason":"Enhanced footer with office hours, professional logos (REALTOR, Equal Housing), and license number"}
|
{"id":"html-3nq","title":"Enhance footer with office hours, professional logos, license numbers","description":"","status":"closed","priority":3,"issue_type":"task","created_at":"2025-11-30T02:24:30.889106857-06:00","updated_at":"2025-11-30T02:46:52.35661921-06:00","closed_at":"2025-11-30T02:46:52.35661921-06:00","close_reason":"Enhanced footer with office hours, professional logos (REALTOR, Equal Housing), and license number"}
|
||||||
{"id":"html-4q8","title":"MLS by HansonXyz Plugin - Phase 6: Admin Interface","description":"Settings page under Settings menu, API token configuration, sync status display, manual sync triggers","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-14T21:02:50.276941526-06:00","updated_at":"2025-12-14T21:21:56.543615901-06:00","closed_at":"2025-12-14T21:21:56.543615901-06:00","dependencies":[{"issue_id":"html-4q8","depends_on_id":"html-3fb","type":"blocks","created_at":"2025-12-14T21:04:10.388690618-06:00","created_by":"unknown"}]}
|
{"id":"html-4q8","title":"MLS by HansonXyz Plugin - Phase 6: Admin Interface","description":"Settings page under Settings menu, API token configuration, sync status display, manual sync triggers","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-14T21:02:50.276941526-06:00","updated_at":"2025-12-14T21:21:56.543615901-06:00","closed_at":"2025-12-14T21:21:56.543615901-06:00","dependencies":[{"issue_id":"html-4q8","depends_on_id":"html-3fb","type":"blocks","created_at":"2025-12-14T21:04:10.388690618-06:00","created_by":"unknown"}]}
|
||||||
@@ -9,6 +10,7 @@
|
|||||||
{"id":"html-5j7","title":"MLS by HansonXyz Plugin - Phase 3: Sync Engine","description":"Sync Engine class, full sync with pagination, incremental sync with ModificationTimestamp, sync state tracking for resume, CLI sync commands","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-14T21:02:34.581442915-06:00","updated_at":"2025-12-14T21:21:46.933578124-06:00","closed_at":"2025-12-14T21:21:46.933578124-06:00","dependencies":[{"issue_id":"html-5j7","depends_on_id":"html-4za","type":"blocks","created_at":"2025-12-14T21:03:55.163778736-06:00","created_by":"unknown"}]}
|
{"id":"html-5j7","title":"MLS by HansonXyz Plugin - Phase 3: Sync Engine","description":"Sync Engine class, full sync with pagination, incremental sync with ModificationTimestamp, sync state tracking for resume, CLI sync commands","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-14T21:02:34.581442915-06:00","updated_at":"2025-12-14T21:21:46.933578124-06:00","closed_at":"2025-12-14T21:21:46.933578124-06:00","dependencies":[{"issue_id":"html-5j7","depends_on_id":"html-4za","type":"blocks","created_at":"2025-12-14T21:03:55.163778736-06:00","created_by":"unknown"}]}
|
||||||
{"id":"html-76m","title":"Remove Bridge Realty text from site","description":"","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-16T11:52:42.455604014-06:00","updated_at":"2025-12-16T11:53:51.821637756-06:00","closed_at":"2025-12-16T11:53:51.821637756-06:00","close_reason":"Removed Bridge Realty text from page-about.php, replaced with correct LandProz DBA info"}
|
{"id":"html-76m","title":"Remove Bridge Realty text from site","description":"","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-16T11:52:42.455604014-06:00","updated_at":"2025-12-16T11:53:51.821637756-06:00","closed_at":"2025-12-16T11:53:51.821637756-06:00","close_reason":"Removed Bridge Realty text from page-about.php, replaced with correct LandProz DBA info"}
|
||||||
{"id":"html-7jz","title":"Add Communities section to navigation and create community pages structure","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-30T02:24:15.204568226-06:00","updated_at":"2025-11-30T02:43:22.075934867-06:00","closed_at":"2025-11-30T02:43:22.075934867-06:00","close_reason":"Created Communities landing page, community page template, 3 community pages, and added to navigation"}
|
{"id":"html-7jz","title":"Add Communities section to navigation and create community pages structure","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-30T02:24:15.204568226-06:00","updated_at":"2025-11-30T02:43:22.075934867-06:00","closed_at":"2025-11-30T02:43:22.075934867-06:00","close_reason":"Created Communities landing page, community page template, 3 community pages, and added to navigation"}
|
||||||
|
{"id":"html-7lr","title":"Property type showcase boxes on homepage","description":"","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-16T13:11:45.764664597-06:00","updated_at":"2025-12-16T13:18:27.291310612-06:00","closed_at":"2025-12-16T13:18:27.291310612-06:00","close_reason":"Created property type showcase boxes section on homepage with 5 categories: Residential, Land, Commercial, Farm, Multi-Family. Each box shows icon, description, count, and links to filtered property archive."}
|
||||||
{"id":"html-98b","title":"Add location search dropdown to homepage hero","description":"","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-30T02:23:59.555310037-06:00","updated_at":"2025-11-30T02:30:59.92891882-06:00","closed_at":"2025-11-30T02:30:59.92891882-06:00","close_reason":"Added location search dropdown to hero section with community taxonomy"}
|
{"id":"html-98b","title":"Add location search dropdown to homepage hero","description":"","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-30T02:23:59.555310037-06:00","updated_at":"2025-11-30T02:30:59.92891882-06:00","closed_at":"2025-11-30T02:30:59.92891882-06:00","close_reason":"Added location search dropdown to hero section with community taxonomy"}
|
||||||
{"id":"html-bfd","title":"Update DESIGN-DOCUMENT.md and IMPLEMENTATION-PLAN.md with RHR structural changes","description":"","status":"closed","priority":0,"issue_type":"task","created_at":"2025-11-30T02:24:40.504170573-06:00","updated_at":"2025-11-30T02:28:50.551587345-06:00","closed_at":"2025-11-30T02:28:50.551587345-06:00","close_reason":"Updated DESIGN-DOCUMENT.md and IMPLEMENTATION-PLAN.md with RHR structural changes"}
|
{"id":"html-bfd","title":"Update DESIGN-DOCUMENT.md and IMPLEMENTATION-PLAN.md with RHR structural changes","description":"","status":"closed","priority":0,"issue_type":"task","created_at":"2025-11-30T02:24:40.504170573-06:00","updated_at":"2025-11-30T02:28:50.551587345-06:00","closed_at":"2025-11-30T02:28:50.551587345-06:00","close_reason":"Updated DESIGN-DOCUMENT.md and IMPLEMENTATION-PLAN.md with RHR structural changes"}
|
||||||
{"id":"html-clv","title":"Analyze Robert Hoffman Realty site structure for HomeProz redesign","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-30T02:11:43.511290155-06:00","updated_at":"2025-11-30T02:21:47.665340956-06:00","closed_at":"2025-11-30T02:21:47.665340956-06:00","close_reason":"Completed site analysis comparing RHR to HomeProz design"}
|
{"id":"html-clv","title":"Analyze Robert Hoffman Realty site structure for HomeProz redesign","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-30T02:11:43.511290155-06:00","updated_at":"2025-11-30T02:21:47.665340956-06:00","closed_at":"2025-11-30T02:21:47.665340956-06:00","close_reason":"Completed site analysis comparing RHR to HomeProz design"}
|
||||||
@@ -21,4 +23,5 @@
|
|||||||
{"id":"html-sbh","title":"MLS by HansonXyz Plugin - Phase 4: Media Handler","description":"Media Handler class, download and organize media files, PhotosChangeTimestamp detection, CLI media commands","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-14T21:02:39.793324508-06:00","updated_at":"2025-12-14T21:21:46.94012466-06:00","closed_at":"2025-12-14T21:21:46.94012466-06:00","dependencies":[{"issue_id":"html-sbh","depends_on_id":"html-5j7","type":"blocks","created_at":"2025-12-14T21:04:00.24472923-06:00","created_by":"unknown"}]}
|
{"id":"html-sbh","title":"MLS by HansonXyz Plugin - Phase 4: Media Handler","description":"Media Handler class, download and organize media files, PhotosChangeTimestamp detection, CLI media commands","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-14T21:02:39.793324508-06:00","updated_at":"2025-12-14T21:21:46.94012466-06:00","closed_at":"2025-12-14T21:21:46.94012466-06:00","dependencies":[{"issue_id":"html-sbh","depends_on_id":"html-5j7","type":"blocks","created_at":"2025-12-14T21:04:00.24472923-06:00","created_by":"unknown"}]}
|
||||||
{"id":"html-t8u","title":"Add Resources section to navigation and create resource pages","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-30T02:24:25.662824938-06:00","updated_at":"2025-11-30T02:45:31.13972652-06:00","closed_at":"2025-11-30T02:45:31.13972652-06:00","close_reason":"Created Resources landing page, resource page template, Buyer's Guide, Seller's Guide, and added to navigation"}
|
{"id":"html-t8u","title":"Add Resources section to navigation and create resource pages","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-30T02:24:25.662824938-06:00","updated_at":"2025-11-30T02:45:31.13972652-06:00","closed_at":"2025-11-30T02:45:31.13972652-06:00","close_reason":"Created Resources landing page, resource page template, Buyer's Guide, Seller's Guide, and added to navigation"}
|
||||||
{"id":"html-x03","title":"MLS by HansonXyz Plugin - Phase 7: Cron \u0026 Automation","description":"WP Cron scheduling (configurable interval), standalone cron script for Unix cron","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-14T21:02:55.483012095-06:00","updated_at":"2025-12-14T21:22:06.519310981-06:00","closed_at":"2025-12-14T21:22:06.519310981-06:00","dependencies":[{"issue_id":"html-x03","depends_on_id":"html-4q8","type":"blocks","created_at":"2025-12-14T21:04:15.476790917-06:00","created_by":"unknown"}]}
|
{"id":"html-x03","title":"MLS by HansonXyz Plugin - Phase 7: Cron \u0026 Automation","description":"WP Cron scheduling (configurable interval), standalone cron script for Unix cron","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-14T21:02:55.483012095-06:00","updated_at":"2025-12-14T21:22:06.519310981-06:00","closed_at":"2025-12-14T21:22:06.519310981-06:00","dependencies":[{"issue_id":"html-x03","depends_on_id":"html-4q8","type":"blocks","created_at":"2025-12-14T21:04:15.476790917-06:00","created_by":"unknown"}]}
|
||||||
|
{"id":"html-xub","title":"Expanded search filters - property type and zip code","description":"","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-16T13:11:40.568406605-06:00","updated_at":"2025-12-16T13:16:29.492622442-06:00","closed_at":"2025-12-16T13:16:29.492622442-06:00","close_reason":"Implemented expanded search filters: MLS property types dropdown (Residential, Land, Commercial Sale, Residential Lease, Residential Income, Farm), city dropdown (107 cities with 50+ listings), and zip code text input field"}
|
||||||
{"id":"html-yxd","title":"Update broker footer legal text","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-16T11:52:52.82531755-06:00","updated_at":"2025-12-16T11:54:46.914199743-06:00","closed_at":"2025-12-16T11:54:46.914199743-06:00","close_reason":"Updated broker footer to 'HomeProz Real Estate LLC DBA LandProz Real Estate, LLC'"}
|
{"id":"html-yxd","title":"Update broker footer legal text","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-16T11:52:52.82531755-06:00","updated_at":"2025-12-16T11:54:46.914199743-06:00","closed_at":"2025-12-16T11:54:46.914199743-06:00","close_reason":"Updated broker footer to 'HomeProz Real Estate LLC DBA LandProz Real Estate, LLC'"}
|
||||||
|
|||||||
@@ -747,9 +747,9 @@ Also: "Broker Brian Haugen - MN | Broker/Auctioneer Greg Jensen - MN, IA - 24-21
|
|||||||
### High Priority (Implement)
|
### High Priority (Implement)
|
||||||
1. Co-listing agent support (1.1)
|
1. Co-listing agent support (1.1)
|
||||||
2. Service area MN + IA (5.3) - COMPLETED 2025-12-16: Updated footer tagline to "Minnesota and Iowa"
|
2. Service area MN + IA (5.3) - COMPLETED 2025-12-16: Updated footer tagline to "Minnesota and Iowa"
|
||||||
3. Expanded search filters (1.5)
|
3. Expanded search filters (1.5) - COMPLETED 2025-12-16: MLS property types, MLS cities (50+ listings), zip code input
|
||||||
4. Property type showcase boxes (1.6)
|
4. Property type showcase boxes (1.6) - COMPLETED 2025-12-16: 5-box grid on homepage (Residential, Land, Commercial, Farm, Multi-Family)
|
||||||
5. Multi-recipient email routing (4.2)
|
5. Multi-recipient email routing (4.2) - COMPLETED 2025-12-16: Primary to office@, CC to info@, sender confirmation receipt
|
||||||
6. Agent page redesign (3.1)
|
6. Agent page redesign (3.1)
|
||||||
7. Geolocation search (1.4)
|
7. Geolocation search (1.4)
|
||||||
|
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -110,6 +110,9 @@ $featured_commercial = new WP_Query(array(
|
|||||||
<?php
|
<?php
|
||||||
// Service Cards Section (Buy/Rent/Sell)
|
// Service Cards Section (Buy/Rent/Sell)
|
||||||
get_template_part('template-parts/components/service-cards');
|
get_template_part('template-parts/components/service-cards');
|
||||||
|
|
||||||
|
// Property Type Showcase Boxes
|
||||||
|
get_template_part('template-parts/components/property-type-boxes');
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<!-- Featured Homes Section (MLS Listings) -->
|
<!-- Featured Homes Section (MLS Listings) -->
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ function homeproz_ajax_filter_properties() {
|
|||||||
$property_type = isset($_POST['property_type']) ? sanitize_text_field($_POST['property_type']) : '';
|
$property_type = isset($_POST['property_type']) ? sanitize_text_field($_POST['property_type']) : '';
|
||||||
$property_status = isset($_POST['property_status']) ? sanitize_text_field($_POST['property_status']) : '';
|
$property_status = isset($_POST['property_status']) ? sanitize_text_field($_POST['property_status']) : '';
|
||||||
$property_location = isset($_POST['property_location']) ? sanitize_text_field($_POST['property_location']) : '';
|
$property_location = isset($_POST['property_location']) ? sanitize_text_field($_POST['property_location']) : '';
|
||||||
|
$zip = isset($_POST['zip']) ? sanitize_text_field($_POST['zip']) : '';
|
||||||
$min_price = isset($_POST['min_price']) ? intval($_POST['min_price']) : '';
|
$min_price = isset($_POST['min_price']) ? intval($_POST['min_price']) : '';
|
||||||
$max_price = isset($_POST['max_price']) ? intval($_POST['max_price']) : '';
|
$max_price = isset($_POST['max_price']) ? intval($_POST['max_price']) : '';
|
||||||
$beds = isset($_POST['beds']) ? intval($_POST['beds']) : '';
|
$beds = isset($_POST['beds']) ? intval($_POST['beds']) : '';
|
||||||
@@ -60,6 +61,9 @@ function homeproz_ajax_filter_properties() {
|
|||||||
if ($property_location) {
|
if ($property_location) {
|
||||||
$filter_args['city'] = $property_location;
|
$filter_args['city'] = $property_location;
|
||||||
}
|
}
|
||||||
|
if ($zip) {
|
||||||
|
$filter_args['postal_code'] = $zip;
|
||||||
|
}
|
||||||
if ($min_price) {
|
if ($min_price) {
|
||||||
$filter_args['min_price'] = $min_price;
|
$filter_args['min_price'] = $min_price;
|
||||||
}
|
}
|
||||||
@@ -151,6 +155,7 @@ function homeproz_ajax_filter_properties() {
|
|||||||
$filter_args = array();
|
$filter_args = array();
|
||||||
if ($property_type) $filter_args['property_type'] = $property_type;
|
if ($property_type) $filter_args['property_type'] = $property_type;
|
||||||
if ($property_location) $filter_args['property_location'] = $property_location;
|
if ($property_location) $filter_args['property_location'] = $property_location;
|
||||||
|
if ($zip) $filter_args['zip'] = $zip;
|
||||||
if ($min_price) $filter_args['min_price'] = $min_price;
|
if ($min_price) $filter_args['min_price'] = $min_price;
|
||||||
if ($max_price) $filter_args['max_price'] = $max_price;
|
if ($max_price) $filter_args['max_price'] = $max_price;
|
||||||
if ($beds) $filter_args['beds'] = $beds;
|
if ($beds) $filter_args['beds'] = $beds;
|
||||||
|
|||||||
@@ -388,6 +388,71 @@ function homeproz_format_mls_listing_for_json($listing, $is_homeproz = false) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get MLS property types for filter dropdowns
|
||||||
|
*
|
||||||
|
* Returns property types with listing counts from MLS database.
|
||||||
|
* Filters to only show types with active listings.
|
||||||
|
*
|
||||||
|
* @return array Array of objects with property_type and count
|
||||||
|
*/
|
||||||
|
function homeproz_get_mls_property_types() {
|
||||||
|
if (!function_exists('mls_plugin')) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$plugin = mls_plugin();
|
||||||
|
$query = $plugin->get_query();
|
||||||
|
|
||||||
|
if (!$query) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->get_property_types('Active');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get MLS cities for filter dropdowns
|
||||||
|
*
|
||||||
|
* Returns distinct cities from MLS database with active listings.
|
||||||
|
* Limited to cities with significant listing counts for usability.
|
||||||
|
*
|
||||||
|
* @param int $min_listings Minimum listings to include city (default 5)
|
||||||
|
* @return array Array of city names
|
||||||
|
*/
|
||||||
|
function homeproz_get_mls_cities($min_listings = 5) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
if (!function_exists('mls_plugin')) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$plugin = mls_plugin();
|
||||||
|
$db = $plugin->get_db();
|
||||||
|
|
||||||
|
if (!$db) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = $db->properties_table();
|
||||||
|
|
||||||
|
// Get cities with at least $min_listings active properties
|
||||||
|
// Also filter to MN and IA only
|
||||||
|
$cities = $wpdb->get_col($wpdb->prepare(
|
||||||
|
"SELECT city FROM {$table}
|
||||||
|
WHERE mlg_can_view = 1
|
||||||
|
AND standard_status = 'Active'
|
||||||
|
AND city IS NOT NULL
|
||||||
|
AND state_or_province IN ('MN', 'IA')
|
||||||
|
GROUP BY city
|
||||||
|
HAVING COUNT(*) >= %d
|
||||||
|
ORDER BY city ASC",
|
||||||
|
$min_listings
|
||||||
|
));
|
||||||
|
|
||||||
|
return $cities ?: array();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get property locations that have active or pending properties
|
* Get property locations that have active or pending properties
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
@import '../template-parts/components/testimonial.scss';
|
@import '../template-parts/components/testimonial.scss';
|
||||||
@import '../template-parts/components/feature-block.scss';
|
@import '../template-parts/components/feature-block.scss';
|
||||||
@import '../template-parts/components/service-cards.scss';
|
@import '../template-parts/components/service-cards.scss';
|
||||||
|
@import '../template-parts/components/property-type-boxes.scss';
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// CSS Custom Properties (Design Tokens)
|
// CSS Custom Properties (Design Tokens)
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Property Type Showcase Boxes
|
||||||
|
*
|
||||||
|
* Displays category boxes for browsing properties by type.
|
||||||
|
* Links to the property archive with type filter applied.
|
||||||
|
*
|
||||||
|
* @package HomeProz
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Prevent direct access
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get property types with counts from MLS
|
||||||
|
$property_types = homeproz_get_mls_property_types();
|
||||||
|
|
||||||
|
if (empty($property_types)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define display configuration for each type
|
||||||
|
// Icons use Feather Icons (already in use throughout the site)
|
||||||
|
$type_config = array(
|
||||||
|
'Residential' => array(
|
||||||
|
'icon' => '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>',
|
||||||
|
'description' => 'Single family homes, condos & townhomes',
|
||||||
|
),
|
||||||
|
'Land' => array(
|
||||||
|
'icon' => '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 22h20"/><path d="M6.36 17.4L4 22h16l-4.24-7.94a.5.5 0 0 0-.88 0L12.5 18.1l-2.5-4.5a.5.5 0 0 0-.88 0z"/><circle cx="13.5" cy="5.5" r="2.5"/></svg>',
|
||||||
|
'description' => 'Vacant lots & acreages',
|
||||||
|
),
|
||||||
|
'Commercial Sale' => array(
|
||||||
|
'icon' => '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="4" y="2" width="16" height="20" rx="2" ry="2"/><path d="M9 22v-4h6v4"/><path d="M8 6h.01M12 6h.01M16 6h.01M8 10h.01M12 10h.01M16 10h.01M8 14h.01M12 14h.01M16 14h.01"/></svg>',
|
||||||
|
'label' => 'Commercial',
|
||||||
|
'description' => 'Office, retail & industrial',
|
||||||
|
),
|
||||||
|
'Farm' => array(
|
||||||
|
'icon' => '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 21h18"/><path d="M5 21V7l7-4 7 4v14"/><path d="M9 21v-6h6v6"/><path d="M10 9h4"/></svg>',
|
||||||
|
'description' => 'Agricultural & hobby farms',
|
||||||
|
),
|
||||||
|
'Residential Income' => array(
|
||||||
|
'icon' => '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9 12h6"/><path d="M9 16h6"/></svg>',
|
||||||
|
'label' => 'Multi-Family',
|
||||||
|
'description' => 'Duplexes, triplexes & apartments',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Filter to only show configured types and sort by count
|
||||||
|
$display_types = array();
|
||||||
|
foreach ($property_types as $type) {
|
||||||
|
if (isset($type_config[$type->property_type])) {
|
||||||
|
$display_types[] = $type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<section class="property-type-boxes">
|
||||||
|
<div class="container">
|
||||||
|
<header class="section-header">
|
||||||
|
<h2 class="section-title">Browse by Property Type</h2>
|
||||||
|
<p class="section-subtitle">Find the perfect property for your needs</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="type-boxes-grid">
|
||||||
|
<?php foreach ($display_types as $type) :
|
||||||
|
$type_name = $type->property_type;
|
||||||
|
$config = $type_config[$type_name];
|
||||||
|
$label = isset($config['label']) ? $config['label'] : $type_name;
|
||||||
|
$count = number_format($type->count);
|
||||||
|
$url = add_query_arg('property_type', urlencode($type_name), home_url('/properties/'));
|
||||||
|
?>
|
||||||
|
<a href="<?php echo esc_url($url); ?>" class="type-box">
|
||||||
|
<div class="type-box-icon">
|
||||||
|
<?php echo $config['icon']; ?>
|
||||||
|
</div>
|
||||||
|
<div class="type-box-content">
|
||||||
|
<h3 class="type-box-title"><?php echo esc_html($label); ?></h3>
|
||||||
|
<p class="type-box-description"><?php echo esc_html($config['description']); ?></p>
|
||||||
|
<span class="type-box-count"><?php echo esc_html($count); ?> listings</span>
|
||||||
|
</div>
|
||||||
|
<div class="type-box-arrow">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M5 12h14M12 5l7 7-7 7"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
* Property Type Boxes Styles
|
||||||
|
*
|
||||||
|
* Grid of clickable category boxes for browsing by property type.
|
||||||
|
*
|
||||||
|
* @package HomeProz
|
||||||
|
*/
|
||||||
|
|
||||||
|
.property-type-boxes {
|
||||||
|
padding: 4rem 0;
|
||||||
|
background-color: var(--color-bg-card);
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
padding: 3rem 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-boxes-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
gap: 1.5rem;
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 1.5rem;
|
||||||
|
background-color: var(--color-bg-dark);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
|
||||||
|
.type-box-icon {
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-box-arrow {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
padding: 1.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-box-icon {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-box-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-box-title {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text);
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-box-description {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
line-height: 1.4;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-box-count {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-accent-light);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-box-arrow {
|
||||||
|
align-self: flex-end;
|
||||||
|
color: var(--color-accent);
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-8px);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -599,6 +599,15 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Also handle text inputs (like zip)
|
||||||
|
this.$form.find('input[type="text"]').each(function() {
|
||||||
|
var name = $(this).attr('name');
|
||||||
|
if (params.has(name)) {
|
||||||
|
$(this).val(params.get(name));
|
||||||
|
hasFilters = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Check for page in hash
|
// Check for page in hash
|
||||||
var page = this.getPageFromHash();
|
var page = this.getPageFromHash();
|
||||||
|
|
||||||
@@ -664,6 +673,7 @@
|
|||||||
nonce: homeprozAjax.nonce,
|
nonce: homeprozAjax.nonce,
|
||||||
property_type: formData.property_type,
|
property_type: formData.property_type,
|
||||||
property_location: formData.property_location,
|
property_location: formData.property_location,
|
||||||
|
zip: formData.zip,
|
||||||
min_price: formData.min_price,
|
min_price: formData.min_price,
|
||||||
max_price: formData.max_price,
|
max_price: formData.max_price,
|
||||||
beds: formData.beds,
|
beds: formData.beds,
|
||||||
@@ -737,6 +747,7 @@
|
|||||||
return {
|
return {
|
||||||
property_type: this.$form.find('[name="property_type"]').val() || '',
|
property_type: this.$form.find('[name="property_type"]').val() || '',
|
||||||
property_location: this.$form.find('[name="property_location"]').val() || '',
|
property_location: this.$form.find('[name="property_location"]').val() || '',
|
||||||
|
zip: this.$form.find('[name="zip"]').val() || '',
|
||||||
min_price: this.$form.find('[name="min_price"]').val() || '',
|
min_price: this.$form.find('[name="min_price"]').val() || '',
|
||||||
max_price: this.$form.find('[name="max_price"]').val() || '',
|
max_price: this.$form.find('[name="max_price"]').val() || '',
|
||||||
beds: this.$form.find('[name="beds"]').val() || ''
|
beds: this.$form.find('[name="beds"]').val() || ''
|
||||||
@@ -1239,6 +1250,7 @@
|
|||||||
nonce: homeprozAjax.nonce,
|
nonce: homeprozAjax.nonce,
|
||||||
property_type: formData.property_type,
|
property_type: formData.property_type,
|
||||||
property_location: formData.property_location,
|
property_location: formData.property_location,
|
||||||
|
zip: formData.zip,
|
||||||
min_price: formData.min_price,
|
min_price: formData.min_price,
|
||||||
max_price: formData.max_price,
|
max_price: formData.max_price,
|
||||||
beds: formData.beds,
|
beds: formData.beds,
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
/**
|
/**
|
||||||
* Property Filters Template Part
|
* Property Filters Template Part
|
||||||
*
|
*
|
||||||
|
* Uses MLS database for property types and cities.
|
||||||
|
*
|
||||||
* @package HomeProz
|
* @package HomeProz
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -12,17 +14,15 @@ if (!defined('ABSPATH')) {
|
|||||||
|
|
||||||
// Get current filter values from URL
|
// Get current filter values from URL
|
||||||
$current_type = isset($_GET['property_type']) ? sanitize_text_field($_GET['property_type']) : '';
|
$current_type = isset($_GET['property_type']) ? sanitize_text_field($_GET['property_type']) : '';
|
||||||
$current_status = isset($_GET['property_status']) ? sanitize_text_field($_GET['property_status']) : '';
|
|
||||||
$current_location = isset($_GET['property_location']) ? sanitize_text_field($_GET['property_location']) : '';
|
$current_location = isset($_GET['property_location']) ? sanitize_text_field($_GET['property_location']) : '';
|
||||||
|
$current_zip = isset($_GET['zip']) ? sanitize_text_field($_GET['zip']) : '';
|
||||||
$current_min_price = isset($_GET['min_price']) ? intval($_GET['min_price']) : '';
|
$current_min_price = isset($_GET['min_price']) ? intval($_GET['min_price']) : '';
|
||||||
$current_max_price = isset($_GET['max_price']) ? intval($_GET['max_price']) : '';
|
$current_max_price = isset($_GET['max_price']) ? intval($_GET['max_price']) : '';
|
||||||
$current_beds = isset($_GET['beds']) ? intval($_GET['beds']) : '';
|
$current_beds = isset($_GET['beds']) ? intval($_GET['beds']) : '';
|
||||||
$current_sort = isset($_GET['sort']) ? sanitize_text_field($_GET['sort']) : 'newest';
|
|
||||||
|
|
||||||
// Get taxonomy terms
|
// Get MLS property types and cities
|
||||||
$property_types = get_terms(array('taxonomy' => 'property_type', 'hide_empty' => false));
|
$property_types = homeproz_get_mls_property_types();
|
||||||
$property_statuses = get_terms(array('taxonomy' => 'property_status', 'hide_empty' => false));
|
$mls_cities = homeproz_get_mls_cities(50); // Cities with 50+ listings
|
||||||
$property_locations = homeproz_get_active_locations();
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="property-filters" id="property-filters" data-ajax-url="<?php echo esc_url(admin_url('admin-ajax.php')); ?>">
|
<div class="property-filters" id="property-filters" data-ajax-url="<?php echo esc_url(admin_url('admin-ajax.php')); ?>">
|
||||||
@@ -33,25 +33,30 @@ $property_locations = homeproz_get_active_locations();
|
|||||||
<select name="property_type" id="filter-type" class="filter-select">
|
<select name="property_type" id="filter-type" class="filter-select">
|
||||||
<option value="">All Types</option>
|
<option value="">All Types</option>
|
||||||
<?php foreach ($property_types as $type) : ?>
|
<?php foreach ($property_types as $type) : ?>
|
||||||
<option value="<?php echo esc_attr($type->slug); ?>" <?php selected($current_type, $type->slug); ?>>
|
<option value="<?php echo esc_attr($type->property_type); ?>" <?php selected($current_type, $type->property_type); ?>>
|
||||||
<?php echo esc_html($type->name); ?>
|
<?php echo esc_html($type->property_type); ?>
|
||||||
</option>
|
</option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filter-item">
|
<div class="filter-item">
|
||||||
<label for="filter-location" class="filter-label">Location</label>
|
<label for="filter-location" class="filter-label">City</label>
|
||||||
<select name="property_location" id="filter-location" class="filter-select">
|
<select name="property_location" id="filter-location" class="filter-select">
|
||||||
<option value="">All</option>
|
<option value="">All Cities</option>
|
||||||
<?php foreach ($property_locations as $location) : ?>
|
<?php foreach ($mls_cities as $city) : ?>
|
||||||
<option value="<?php echo esc_attr($location->slug); ?>" <?php selected($current_location, $location->slug); ?>>
|
<option value="<?php echo esc_attr($city); ?>" <?php selected($current_location, $city); ?>>
|
||||||
<?php echo esc_html($location->name); ?>
|
<?php echo esc_html($city); ?>
|
||||||
</option>
|
</option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-item filter-item-zip">
|
||||||
|
<label for="filter-zip" class="filter-label">Zip Code</label>
|
||||||
|
<input type="text" name="zip" id="filter-zip" class="filter-input" placeholder="e.g. 55401" value="<?php echo esc_attr($current_zip); ?>" maxlength="10" pattern="[0-9\-]*">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="filter-item">
|
<div class="filter-item">
|
||||||
<label for="filter-beds" class="filter-label">Beds</label>
|
<label for="filter-beds" class="filter-label">Beds</label>
|
||||||
<select name="beds" id="filter-beds" class="filter-select">
|
<select name="beds" id="filter-beds" class="filter-select">
|
||||||
|
|||||||
@@ -485,6 +485,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.625rem 0.75rem;
|
||||||
|
background-color: var(--color-bg-dark);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
color: var(--color-text);
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zip code filter - narrower width
|
||||||
|
.filter-item-zip {
|
||||||
|
max-width: 120px;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Loading State
|
// Loading State
|
||||||
.property-filters.is-loading {
|
.property-filters.is-loading {
|
||||||
|
|||||||
Reference in New Issue
Block a user