diff --git a/Status.md b/Status.md index 9db8f2f5..e4bdb8cc 100644 --- a/Status.md +++ b/Status.md @@ -1,14 +1,57 @@ # HVAC Community Events - Project Status **Last Updated:** February 1, 2026 -**Current Session:** Find Training Page Implementation - Complete -**Version:** 2.2.0 (Ready for Staging Deployment) +**Current Session:** Find Training Page Enhancements - Complete +**Version:** 2.2.4 (Deployed to Production) --- -## 🎯 CURRENT SESSION - FIND TRAINING PAGE IMPLEMENTATION (Jan 31 - Feb 1, 2026) +## 🎯 CURRENT SESSION - FIND TRAINING PAGE ENHANCEMENTS (Feb 1, 2026) -### Status: ✅ **COMPLETE - Ready for Staging Deployment & E2E Testing** +### Status: ✅ **COMPLETE - Deployed to Production** + +**Objective:** Improve Find Training page UX with viewport sync and marker hover interactions. + +### Changes Made + +1. ✅ **Viewport Sync** - Sidebar now shows only trainers visible in current map area + - Added `visibleTrainers` array to track filtered trainers + - Added `syncSidebarWithViewport()` method filtering by map bounds + - Map `idle` event triggers sync on pan/zoom + - Count shows "X of Y trainers" when zoomed in + +2. ✅ **Marker Hover Interaction** - Info window appears on hover + - Added `mouseover` event listener to trainer/venue markers + - Set `optimized: false` on markers for reliable hover events + - Hover shows info window preview with "View Profile" button + - Click on "View Profile" opens full modal with contact form + +3. ✅ **Legacy URL Redirects** + - `/find-a-trainer/` → `/find-training/` (301 redirect) + - `/find-trainer/` → `/find-training/` (301 redirect) + - Removed old page from Page Manager + +### Files Modified + +| File | Change | +|------|--------| +| `assets/js/find-training-map.js` | Added viewport sync, hover events, optimized:false | +| `assets/js/find-training-filters.js` | Updated filter handler for visibleTrainers | +| `includes/class-hvac-route-manager.php` | Added legacy URL redirects | +| `includes/class-hvac-page-manager.php` | Removed find-a-trainer page definition | +| `includes/class-hvac-plugin.php` | Version bumped to 2.2.4 | + +### Verified Behavior +- ✅ Hover over marker → Info window appears immediately +- ✅ Click "View Profile" → Full modal with trainer details + contact form +- ✅ Pan/zoom map → Sidebar updates to show visible trainers only +- ✅ Legacy URLs redirect to new page + +--- + +## 📋 PREVIOUS SESSION - FIND TRAINING PAGE IMPLEMENTATION (Jan 31 - Feb 1, 2026) + +### Status: ✅ **COMPLETE - Deployed to Production** **Objective:** Replace the buggy MapGeo-based `/find-a-trainer` page with a new `/find-training` page built from scratch using Google Maps JavaScript API. @@ -69,13 +112,12 @@ Ran comprehensive code review using GPT-5, Gemini 3, and Zen MCP tools. Found an - ✅ Auto-geocoding for new venues - ✅ Rate-limited batch geocoding for existing venues -### Next Steps -1. ⏳ Deploy to staging: `./scripts/deploy.sh staging` -2. ⏳ Run E2E tests on Find Training page -3. ⏳ Verify map loads with markers -4. ⏳ Test filters and geolocation -5. ⏳ Verify contact form sends email -6. ⏳ Deploy to production after validation +### Deployment Status +- ✅ Deployed to staging +- ✅ Map loads with markers and clustering +- ✅ Filters working (state, certification, format) +- ✅ Contact form functional +- ✅ Deployed to production --- diff --git a/assets/js/find-training-filters.js b/assets/js/find-training-filters.js index 23638985..a24c5a6f 100644 --- a/assets/js/find-training-filters.js +++ b/assets/js/find-training-filters.js @@ -36,6 +36,7 @@ */ init: function() { this.bindEvents(); + this.initMobileFilterToggle(); }, /** @@ -133,12 +134,11 @@ // Update map data HVACTrainingMap.trainers = response.data.trainers || []; HVACTrainingMap.venues = response.data.venues || []; + HVACTrainingMap.visibleTrainers = HVACTrainingMap.trainers.slice(); // Reset to all HVACTrainingMap.updateMarkers(); - HVACTrainingMap.updateCounts( - response.data.total_trainers, - response.data.total_venues - ); + HVACTrainingMap.updateCounts(HVACTrainingMap.trainers.length); HVACTrainingMap.updateTrainerGrid(); + // Note: syncSidebarWithViewport will be called by map 'idle' event } }, complete: function() { @@ -375,6 +375,66 @@ const div = document.createElement('div'); div.textContent = text; return div.innerHTML; + }, + + /** + * Initialize mobile filter toggle + */ + initMobileFilterToggle: function() { + const self = this; + + // Mobile filter panel toggle + $(document).on('click', '.hvac-mobile-filter-toggle', function() { + const $toggle = $(this); + const $panel = $('#hvac-mobile-filter-panel'); + const isExpanded = $toggle.attr('aria-expanded') === 'true'; + + if (isExpanded) { + $panel.attr('hidden', ''); + $toggle.attr('aria-expanded', 'false'); + } else { + $panel.removeAttr('hidden'); + $toggle.attr('aria-expanded', 'true'); + } + }); + + // Sync mobile filter selects with desktop selects + $('#hvac-filter-state-mobile').on('change', function() { + const value = $(this).val(); + $('#hvac-filter-state').val(value); + self.activeFilters.state = value; + self.applyFilters(); + self.updateActiveFiltersDisplay(); + }); + + $('#hvac-filter-certification-mobile').on('change', function() { + const value = $(this).val(); + $('#hvac-filter-certification').val(value); + self.activeFilters.certification = value; + self.applyFilters(); + self.updateActiveFiltersDisplay(); + }); + + $('#hvac-filter-format-mobile').on('change', function() { + const value = $(this).val(); + $('#hvac-filter-format').val(value); + self.activeFilters.training_format = value; + self.applyFilters(); + self.updateActiveFiltersDisplay(); + }); + + // Also sync desktop to mobile when desktop changes + $('#hvac-filter-state').on('change', function() { + $('#hvac-filter-state-mobile').val($(this).val()); + }); + + $('#hvac-filter-certification').on('change', function() { + $('#hvac-filter-certification-mobile').val($(this).val()); + }); + + $('#hvac-filter-format').on('change', function() { + $('#hvac-filter-format-mobile').val($(this).val()); + }); } }; diff --git a/assets/js/find-training-map.js b/assets/js/find-training-map.js index 695189d3..47ee79a0 100644 --- a/assets/js/find-training-map.js +++ b/assets/js/find-training-map.js @@ -31,6 +31,9 @@ trainers: [], venues: [], + // Visible trainers (filtered by map bounds) + visibleTrainers: [], + // Configuration config: { mapElementId: 'hvac-training-map', @@ -45,21 +48,29 @@ init: function() { const self = this; + // Check if API key is configured + if (typeof hvacFindTraining === 'undefined' || !hvacFindTraining.api_key_configured) { + console.warn('Google Maps API key not configured'); + // Still load trainer directory data + this.loadTrainerDirectory(); + return; + } + // Check if Google Maps is loaded if (typeof google === 'undefined' || typeof google.maps === 'undefined') { console.error('Google Maps API not loaded'); this.showMapError('Google Maps failed to load. Please refresh the page.'); + // Still load trainer directory data + this.loadTrainerDirectory(); return; } // Override config with localized data - if (typeof hvacFindTraining !== 'undefined') { - if (hvacFindTraining.map_center) { - this.config.defaultCenter = hvacFindTraining.map_center; - } - if (hvacFindTraining.default_zoom) { - this.config.defaultZoom = parseInt(hvacFindTraining.default_zoom); - } + if (hvacFindTraining.map_center) { + this.config.defaultCenter = hvacFindTraining.map_center; + } + if (hvacFindTraining.default_zoom) { + this.config.defaultZoom = parseInt(hvacFindTraining.default_zoom); } // Create the map @@ -73,6 +84,36 @@ // Bind events this.bindEvents(); + + // Initialize responsive features + this.handleWindowResize(); + this.initSidebarToggle(); + }, + + /** + * Load just the trainer directory (no map) + */ + loadTrainerDirectory: function() { + const self = this; + + $.ajax({ + url: hvacFindTraining.ajax_url, + type: 'POST', + data: { + action: 'hvac_get_training_map_data', + nonce: hvacFindTraining.nonce + }, + success: function(response) { + if (response.success && response.data) { + self.trainers = response.data.trainers || []; + self.updateTrainerGrid(self.trainers); + self.updateCounts(self.trainers.length, 0); + } + }, + error: function() { + self.hideLoading(); + } + }); }, /** @@ -110,6 +151,12 @@ this.map.addListener('click', () => { this.infoWindow.close(); }); + + // Sync sidebar with map viewport on pan/zoom + const self = this; + this.map.addListener('idle', function() { + self.syncSidebarWithViewport(); + }); }, /** @@ -157,10 +204,13 @@ if (response.success) { self.trainers = response.data.trainers || []; self.venues = response.data.venues || []; + self.visibleTrainers = self.trainers.slice(); // Initially all trainers are "visible" self.updateMarkers(); - self.updateCounts(response.data.total_trainers, response.data.total_venues); + self.updateCounts(self.trainers.length); self.updateTrainerGrid(); + // Note: syncSidebarWithViewport will be called by map 'idle' event + // to filter trainers to current viewport } else { self.showMapError(response.data?.message || 'Failed to load data'); } @@ -222,14 +272,19 @@ map: this.map, title: trainer.name, icon: this.getTrainerIcon(), - optimized: true + optimized: false // Required for reliable hover events }); // Store trainer data on marker marker.trainerData = trainer; marker.markerType = 'trainer'; - // Add click listener + // Add hover listener to show info window preview + marker.addListener('mouseover', function() { + self.showTrainerInfoWindow(this); + }); + + // Add click listener (also shows info window, for touch devices) marker.addListener('click', function() { self.showTrainerInfoWindow(this); }); @@ -249,14 +304,19 @@ map: this.map, title: venue.name, icon: this.getVenueIcon(), - optimized: true + optimized: false // Required for reliable hover events }); // Store venue data on marker marker.venueData = venue; marker.markerType = 'venue'; - // Add click listener + // Add hover listener to show info window preview + marker.addListener('mouseover', function() { + self.showVenueInfoWindow(this); + }); + + // Add click listener (also shows info window, for touch devices) marker.addListener('click', function() { self.showVenueInfoWindow(this); }); @@ -585,21 +645,30 @@ const $grid = $('#hvac-trainer-grid'); $grid.empty(); - if (this.trainers.length === 0) { - $grid.html('
No trainers found matching your criteria.
' + message + '