From 5c15b27935f0687feb2d4a138f8009be7a6378d4 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 1 Feb 2026 13:36:06 -0400 Subject: [PATCH] feat(find-training): measureQuick Approved Training Labs implementation Add venue taxonomies and filter /find-training to show only approved labs: - Create venue_type, venue_equipment, venue_amenities taxonomies - Filter venue markers by mq-approved-lab taxonomy term - Add equipment and amenities badges to venue modal - Add venue contact form with AJAX handler and email notification - Include POC (Point of Contact) meta for each training lab 9 approved training labs configured: - Fast Track Learning Lab, Progressive Training Lab, NAVAC Technical Training Center - Stevens Equipment Phoenix/Johnstown, San Jacinto College, Johnstone Supply - TruTech Tools Training Center (new), Auer Steel & Heating Supply (new) Note: Venues not displaying on map - to be debugged next session. Co-Authored-By: Claude Opus 4.5 --- Status.md | 81 +- assets/css/find-training-map.css | 1289 ++++++++++++----- assets/js/find-training-map.js | 129 +- includes/class-hvac-ajax-handlers.php | 175 +++ includes/class-hvac-plugin.php | 8 + includes/class-hvac-venue-categories.php | 282 ++++ .../class-hvac-training-map-data.php | 38 +- templates/page-find-training.php | 375 +++-- 8 files changed, 1859 insertions(+), 518 deletions(-) create mode 100644 includes/class-hvac-venue-categories.php diff --git a/Status.md b/Status.md index e4bdb8cc..9eafa7ca 100644 --- a/Status.md +++ b/Status.md @@ -1,12 +1,87 @@ # HVAC Community Events - Project Status **Last Updated:** February 1, 2026 -**Current Session:** Find Training Page Enhancements - Complete -**Version:** 2.2.4 (Deployed to Production) +**Current Session:** measureQuick Approved Training Labs Implementation +**Version:** 2.3.0 (Deployed to Staging) --- -## 🎯 CURRENT SESSION - FIND TRAINING PAGE ENHANCEMENTS (Feb 1, 2026) +## 🎯 CURRENT SESSION - MEASUREQUICK APPROVED TRAINING LABS (Feb 1, 2026) + +### Status: 🔄 **IN PROGRESS - Deployed to Staging, Venues Not Displaying** + +**Objective:** Transform /find-training to showcase only measureQuick Approved Training Labs with venue categories, equipment/amenities tags, and contact forms. + +### Changes Made + +1. ✅ **Venue Taxonomies Created** (`includes/class-hvac-venue-categories.php` NEW) + - `venue_type` - For lab classification (e.g., "measureQuick Approved Training Lab") + - `venue_equipment` - Furnace, Heat Pump, AC, Mini-Split, Boiler, etc. + - `venue_amenities` - Coffee, Water, Projector, WiFi, Parking, etc. + - All taxonomies registered on `tribe_venue` post type + +2. ✅ **9 Approved Training Labs Configured** (via WP-CLI script) + - Fast Track Learning Lab (ID: 6631) - Joe Medosch + - Progressive Training Lab (ID: 6284) - Samantha Brazie + - NAVAC Technical Training Center (ID: 6476) - Andrew Greaves + - Stevens Equipment Supply - Phoenix (ID: 6448) - Robert Cone + - San Jacinto College South Campus (ID: 6521) - Terry McWilliams + - Johnstone Supply - Live Fire Training Lab (ID: 4864) - Dave Petz + - Stevens Equipment Supply - Johnstown (ID: 1648) - Phil Sweren + - TruTech Tools Training Center (NEW) - Val Buckles + - Auer Steel & Heating Supply (NEW) - Mike Breen + +3. ✅ **Map Data Filtered by Taxonomy** + - `get_venue_markers()` now includes `tax_query` for `mq-approved-lab` term + - Only approved training labs appear as venue markers + - Equipment, amenities, and POC data included in venue info + +4. ✅ **Venue Modal Enhanced** + - Equipment badges (teal outline chips) + - Amenities badges (gray outline chips) + - Contact form with name, email, phone, company, message + - POC receives email notification on submission + +5. ✅ **6 POC Trainer Accounts Created** + - Samantha Brazie, Andrew Greaves, Terry McWilliams + - Phil Sweren, Robert Cone, Dave Petz + - All with `hvac_trainer` role and approved status + +### Files Created + +| File | Description | +|------|-------------| +| `includes/class-hvac-venue-categories.php` | Venue taxonomy registration (singleton) | +| `scripts/setup-approved-labs.php` | WP-CLI script to configure all labs | + +### Files Modified + +| File | Change | +|------|--------| +| `includes/class-hvac-plugin.php` | Load venue categories class | +| `includes/find-training/class-hvac-training-map-data.php` | Add tax_query filter, include equipment/amenities | +| `includes/class-hvac-ajax-handlers.php` | Add venue contact form AJAX handler | +| `templates/page-find-training.php` | Add equipment/amenities badges, contact form | +| `assets/js/find-training-map.js` | Render badges, bind contact form handler | +| `assets/css/find-training-map.css` | Equipment/amenities badge styles | + +### Known Issue: Venues Not Displaying on Map + +**Symptom:** Map at `/find-training` shows no venue markers despite data being correctly configured. + +**Verified:** +- ✅ 9 venues have `mq-approved-lab` taxonomy term assigned +- ✅ API endpoint returns 9 approved labs correctly +- ✅ Equipment and amenities data populated + +**To Debug Next Session:** +- Check JavaScript console for marker rendering errors +- Verify Google Maps marker creation for venues +- Check venue toggle state (`#hvac-show-venues`) + +--- + +## 📋 PREVIOUS SESSION - FIND TRAINING PAGE ENHANCEMENTS (Feb 1, 2026) ### Status: ✅ **COMPLETE - Deployed to Production** diff --git a/assets/css/find-training-map.css b/assets/css/find-training-map.css index 37631692..7549737e 100644 --- a/assets/css/find-training-map.css +++ b/assets/css/find-training-map.css @@ -1,189 +1,187 @@ /** * Find Training Page Styles * - * Styles for the Find Training page with Google Maps integration. + * Google Maps-style full-screen layout with sidebar and compact filter bar. * * @package HVAC_Community_Events * @since 2.2.0 */ /* ========================================================================== - Page Layout + CSS Custom Properties ========================================================================== */ -.hvac-find-training-page { - padding: 30px 0 60px; +:root { + --hvac-header-offset: 0px; + --hvac-primary: #00b3a4; + --hvac-primary-dark: #009688; + --hvac-secondary: #164B60; + --hvac-secondary-dark: #1a5a73; + --hvac-venue-color: #f5a623; + --hvac-text: #333; + --hvac-text-muted: #666; + --hvac-border: #e0e0e0; + --hvac-sidebar-width: 380px; + --hvac-filter-bar-height: auto; } -.hvac-find-training-page .ast-container { - max-width: 1400px; +/* Astra theme header offset */ +body:not(.ast-header-break-point) .hvac-find-training-page { + --hvac-header-offset: 0px; } -.hvac-find-training-page .hvac-page-title { - color: #164B60; - font-size: 2.5rem; - font-weight: 700; - margin-bottom: 20px; - text-align: center; +/* WordPress admin bar adjustment */ +.admin-bar .hvac-find-training-page { + --hvac-header-offset: 32px; } -/* Intro Section */ -.hvac-find-training-intro { - max-width: 900px; - margin: 0 auto 30px; - text-align: center; - color: #333; - line-height: 1.7; -} - -.hvac-find-training-intro p { - margin-bottom: 12px; -} - -/* ========================================================================== - Map and Filters Layout - ========================================================================== */ - -.hvac-map-filters-wrapper { - display: grid; - grid-template-columns: 2fr 1fr; - gap: 30px; - margin-bottom: 40px; -} - -@media (max-width: 991px) { - .hvac-map-filters-wrapper { - grid-template-columns: 1fr; +@media (max-width: 782px) { + .admin-bar .hvac-find-training-page { + --hvac-header-offset: 46px; } } /* ========================================================================== - Map Section + Skip Link (Accessibility) ========================================================================== */ -.hvac-map-section { - position: relative; +.hvac-skip-link { + position: absolute; + top: -40px; + left: 0; + background: var(--hvac-secondary); + color: #fff; + padding: 8px 16px; + z-index: 100000; + transition: top 0.2s; } -.hvac-google-map { - width: 100%; - height: 500px; - background: #f5f5f5; - border-radius: 8px; - overflow: hidden; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); -} - -.hvac-map-loading { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 100%; - color: #666; -} - -.hvac-map-loading .dashicons { - font-size: 48px; - width: 48px; - height: 48px; - margin-bottom: 15px; - color: #00b3a4; -} - -/* Map Legend */ -.hvac-map-legend { - display: flex; - gap: 20px; - padding: 12px 16px; - background: #fff; - border-radius: 0 0 8px 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08); -} - -.hvac-legend-item { - display: flex; - align-items: center; - gap: 8px; - font-size: 14px; - color: #555; -} - -.hvac-legend-marker { - width: 16px; - height: 16px; - border-radius: 50%; -} - -.hvac-legend-trainer { - background: #00b3a4; -} - -.hvac-legend-venue { - background: #f5a623; +.hvac-skip-link:focus { + top: 0; + outline: 2px solid var(--hvac-primary); + outline-offset: 2px; } /* ========================================================================== - Filters Section + Page Layout - Full Height ========================================================================== */ -.hvac-filters-section { +.hvac-find-training-page { + display: flex !important; + flex-direction: column !important; + height: 100vh !important; + height: 100dvh !important; /* Modern browsers - accounts for mobile browser UI */ + overflow: hidden !important; + position: relative !important; + padding: 0 !important; + margin: 0 !important; +} + +/* Remove default Astra container constraints */ +.hvac-find-training-page .ast-container, +.hvac-find-training-page > .ast-container, +body .hvac-find-training-page { + max-width: none !important; + padding: 0 !important; + width: 100% !important; +} + +/* ========================================================================== + Filter Bar - Fixed at Top + ========================================================================== */ + +.hvac-filter-bar { + flex-shrink: 0; background: #fff; - border-radius: 8px; - padding: 24px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border-bottom: 1px solid var(--hvac-border); + padding: 12px 16px; + z-index: 100; } -/* Search Box */ -.hvac-search-box { +.hvac-filter-bar-inner { + display: flex !important; + flex-direction: row !important; + gap: 12px; + align-items: center; + flex-wrap: wrap; + max-width: 1600px; + margin: 0 auto; +} + +/* Search Input */ +.hvac-filter-search { position: relative; - margin-bottom: 16px; + flex: 1; + min-width: 180px; + max-width: 300px; } -.hvac-search-input { +.hvac-filter-search .dashicons { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + color: #999; + pointer-events: none; +} + +.hvac-filter-search input { width: 100%; - padding: 12px 40px 12px 16px; - font-size: 15px; - border: 1px solid #ddd; + padding: 10px 12px 10px 38px; + font-size: 14px; + border: 1px solid var(--hvac-border); border-radius: 6px; transition: border-color 0.2s, box-shadow 0.2s; } -.hvac-search-input:focus { +.hvac-filter-search input:focus { outline: none; - border-color: #00b3a4; - box-shadow: 0 0 0 3px rgba(0, 179, 164, 0.1); + border-color: var(--hvac-primary); + box-shadow: 0 0 0 3px rgba(0, 179, 164, 0.15); } -.hvac-search-box .dashicons { - position: absolute; - right: 12px; - top: 50%; - transform: translateY(-50%); - color: #999; +/* Filter Dropdowns */ +.hvac-filter-dropdowns { + display: flex !important; + flex-direction: row !important; + gap: 10px; +} + +.hvac-filter-select { + padding: 10px 32px 10px 12px; + font-size: 14px; + border: 1px solid var(--hvac-border); + border-radius: 6px; + background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M2 4l4 4 4-4'/%3E%3C/svg%3E") no-repeat right 10px center; + cursor: pointer; + min-width: 130px; + appearance: none; +} + +.hvac-filter-select:focus { + outline: none; + border-color: var(--hvac-primary); } /* Near Me Button */ .hvac-near-me-btn { - width: 100%; - padding: 12px 16px; - background: #164B60; + display: flex; + align-items: center; + gap: 6px; + padding: 10px 16px; + background: var(--hvac-secondary); color: #fff; border: none; border-radius: 6px; - font-size: 15px; - font-weight: 600; + font-size: 14px; + font-weight: 500; cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - margin-bottom: 20px; transition: background 0.2s; + white-space: nowrap; } .hvac-near-me-btn:hover { - background: #1a5a73; + background: var(--hvac-secondary-dark); } .hvac-near-me-btn:disabled { @@ -191,138 +189,79 @@ cursor: not-allowed; } +.hvac-near-me-btn.active { + background: var(--hvac-primary); +} + .hvac-near-me-btn .dashicons { - font-size: 18px; -} - -/* Filters Header */ -.hvac-filters-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 16px; -} - -.hvac-filters-label { - font-weight: 600; - color: #333; + font-size: 16px; + width: 16px; + height: 16px; } +/* Clear Filters Button */ .hvac-clear-filters { - background: none; - border: none; - color: #00b3a4; - font-size: 13px; + padding: 10px 14px; + background: transparent; + color: var(--hvac-primary); + border: 1px solid var(--hvac-primary); + border-radius: 6px; + font-size: 14px; + font-weight: 500; cursor: pointer; - padding: 0; + transition: all 0.2s; } .hvac-clear-filters:hover { - text-decoration: underline; + background: var(--hvac-primary); + color: #fff; } -/* Filter Groups */ -.hvac-filter-group { - margin-bottom: 16px; -} - -.hvac-filter-group label { - display: block; - font-size: 13px; - font-weight: 500; - color: #555; - margin-bottom: 6px; -} - -.hvac-filter-select { - width: 100%; - padding: 10px 12px; - font-size: 14px; - border: 1px solid #ddd; - border-radius: 6px; - background: #fff; - cursor: pointer; -} - -.hvac-filter-select:focus { - outline: none; - border-color: #00b3a4; -} - -/* Marker Toggles */ -.hvac-marker-toggles { - margin: 20px 0; - padding: 16px 0; - border-top: 1px solid #eee; - border-bottom: 1px solid #eee; -} - -.hvac-toggle { - display: flex; +/* Mobile Filter Toggle - Hidden on Desktop */ +.hvac-mobile-filter-toggle { + display: none !important; align-items: center; - cursor: pointer; - margin-bottom: 10px; -} - -.hvac-toggle:last-child { - margin-bottom: 0; -} - -.hvac-toggle input { - position: absolute; - opacity: 0; - pointer-events: none; -} - -.hvac-toggle-slider { - width: 40px; - height: 22px; - background: #ccc; - border-radius: 11px; - position: relative; - transition: background 0.2s; - margin-right: 10px; - flex-shrink: 0; -} - -.hvac-toggle-slider::after { - content: ''; - position: absolute; - top: 2px; - left: 2px; - width: 18px; - height: 18px; - background: #fff; - border-radius: 50%; - transition: transform 0.2s; -} - -.hvac-toggle input:checked + .hvac-toggle-slider { - background: #00b3a4; -} - -.hvac-toggle input:checked + .hvac-toggle-slider::after { - transform: translateX(18px); -} - -.hvac-toggle-label { + gap: 6px; + padding: 10px 14px; + background: #f5f5f5; + color: var(--hvac-text); + border: 1px solid var(--hvac-border); + border-radius: 6px; font-size: 14px; - color: #333; + cursor: pointer; } -/* Active Filters */ +/* Sidebar Toggle - Hidden on Desktop */ +.hvac-sidebar-toggle { + display: none !important; +} + +.hvac-mobile-filter-toggle .dashicons { + font-size: 16px; + width: 16px; + height: 16px; +} + +/* Active Filters Chips */ .hvac-active-filters { display: flex; flex-wrap: wrap; gap: 8px; - margin-bottom: 16px; + margin-top: 8px; + max-width: 1600px; + margin-left: auto; + margin-right: auto; +} + +.hvac-active-filters:empty { + display: none; } .hvac-active-filter { display: inline-flex; align-items: center; gap: 6px; - padding: 6px 10px; + padding: 5px 10px; background: #e8f5f4; color: #00736a; border-radius: 20px; @@ -336,42 +275,138 @@ cursor: pointer; padding: 0; line-height: 1; + font-size: 16px; + opacity: 0.7; } -/* Results Count */ -.hvac-results-count { - text-align: center; - color: #666; - font-size: 14px; - padding-top: 16px; - border-top: 1px solid #eee; +.hvac-active-filter button:hover { + opacity: 1; +} + +/* Mobile Filter Panel */ +.hvac-mobile-filter-panel { + display: none; + padding: 16px 0; + border-top: 1px solid var(--hvac-border); + margin-top: 12px; +} + +.hvac-mobile-filter-panel:not([hidden]) { + display: flex; + flex-direction: column; + gap: 12px; +} + +.hvac-mobile-filter-group label { + display: block; + font-size: 13px; + font-weight: 500; + color: var(--hvac-text-muted); + margin-bottom: 6px; +} + +.hvac-mobile-filter-group .hvac-filter-select { + width: 100%; } /* ========================================================================== - Trainer Directory Grid + Main Layout - CSS Grid for Sidebar + Map ========================================================================== */ -.hvac-trainer-directory-section { - margin-bottom: 40px; +.hvac-map-layout { + flex: 1 !important; + display: grid !important; + grid-template-areas: "sidebar map" !important; + grid-template-columns: var(--hvac-sidebar-width) 1fr !important; + overflow: hidden !important; + min-height: 0 !important; /* Prevent grid blowout */ } -.hvac-trainer-directory-section h2 { - color: #164B60; - font-size: 1.75rem; - margin-bottom: 24px; +/* ========================================================================== + Left Sidebar + ========================================================================== */ + +.hvac-sidebar { + grid-area: sidebar !important; + background: #fff; + border-right: 1px solid var(--hvac-border); + display: flex !important; + flex-direction: column !important; + overflow: hidden !important; + min-height: 0 !important; + width: var(--hvac-sidebar-width) !important; } -.hvac-trainer-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - gap: 24px; +.hvac-sidebar-header { + flex-shrink: 0; + display: flex; + justify-content: space-between; + align-items: center; + padding: 14px 16px; + background: #f9fafb; + border-bottom: 1px solid var(--hvac-border); } +.hvac-results-summary { + font-size: 14px; + font-weight: 600; + color: var(--hvac-secondary); +} + +.hvac-results-summary #hvac-trainer-count { + font-size: 18px; +} + +/* Sidebar Toggle styling (visibility controlled by media queries) */ +.hvac-sidebar-toggle.hvac-sidebar-toggle { + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + background: transparent; + border: none; + cursor: pointer; + border-radius: 4px; +} + +.hvac-sidebar-toggle:hover { + background: #eee; +} + +.hvac-sidebar-toggle .dashicons { + font-size: 20px; + width: 20px; + height: 20px; + color: var(--hvac-text-muted); + transition: transform 0.2s; +} + +.hvac-sidebar.collapsed .hvac-sidebar-toggle .dashicons { + transform: rotate(180deg); +} + +/* Sidebar Content - Scrollable */ +.hvac-sidebar-content { + flex: 1; + overflow-y: auto; + padding: 16px; + display: flex; + flex-direction: column; + gap: 16px; +} + +/* Trainer List */ +.hvac-trainer-list { + display: flex; + flex-direction: column; + gap: 12px; +} + +/* Loading State */ .hvac-grid-loading { - grid-column: 1 / -1; text-align: center; - padding: 40px; - color: #666; + padding: 40px 20px; + color: var(--hvac-text-muted); } .hvac-spin { @@ -383,26 +418,26 @@ to { transform: rotate(360deg); } } -/* Trainer Card */ +/* Trainer Card - Compact for Sidebar */ .hvac-trainer-card { background: #fff; + border: 1px solid var(--hvac-border); border-radius: 8px; - padding: 20px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + padding: 14px; cursor: pointer; - transition: transform 0.2s, box-shadow 0.2s; + transition: border-color 0.2s, box-shadow 0.2s; display: flex; - gap: 16px; + gap: 12px; } .hvac-trainer-card:hover { - transform: translateY(-2px); - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); + border-color: var(--hvac-primary); + box-shadow: 0 2px 8px rgba(0, 179, 164, 0.15); } .hvac-trainer-card-image { - width: 80px; - height: 80px; + width: 56px; + height: 56px; flex-shrink: 0; position: relative; } @@ -425,16 +460,16 @@ } .hvac-trainer-card-avatar .dashicons { - font-size: 32px; - color: #00b3a4; + font-size: 24px; + color: var(--hvac-primary); } .hvac-mq-badge-small { position: absolute; - bottom: 0; - right: 0; - width: 24px; - height: 24px; + bottom: -2px; + right: -2px; + width: 20px; + height: 20px; } .hvac-trainer-card-info { @@ -444,54 +479,194 @@ .hvac-trainer-card-name { font-weight: 600; - color: #164B60; - margin-bottom: 4px; - font-size: 16px; + color: var(--hvac-secondary); + margin-bottom: 3px; + font-size: 15px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .hvac-trainer-card-location { - color: #666; - font-size: 14px; - margin-bottom: 8px; + color: var(--hvac-text-muted); + font-size: 13px; + margin-bottom: 6px; } .hvac-trainer-card-certs { display: flex; flex-wrap: wrap; - gap: 6px; + gap: 4px; } .hvac-cert-badge { display: inline-block; - padding: 4px 8px; + padding: 2px 6px; background: #e8f5f4; color: #00736a; - border-radius: 4px; - font-size: 12px; + border-radius: 3px; + font-size: 11px; font-weight: 500; } -/* Load More */ +/* No Results */ +.hvac-no-results { + text-align: center; + padding: 30px 20px; + color: var(--hvac-text-muted); +} + +/* Load More Button */ .hvac-load-more-wrapper { text-align: center; - margin-top: 24px; + padding-top: 8px; +} + +#hvac-load-more { + width: 100%; +} + +/* Sidebar CTA */ +.hvac-sidebar-cta { + margin-top: auto; + padding: 16px; + background: #f8fafa; + border-radius: 8px; + text-align: center; +} + +.hvac-sidebar-cta p { + font-size: 14px; + color: var(--hvac-text); + margin: 0 0 12px; +} + +.hvac-btn-small { + padding: 10px 20px; + font-size: 14px; } /* ========================================================================== - CTA Section + Map Container ========================================================================== */ -.hvac-cta-section { - text-align: center; - padding: 40px; - background: #f8fafa; - border-radius: 8px; +.hvac-map-container { + grid-area: map; + position: relative; + min-height: 0; /* Prevent grid blowout */ } -.hvac-cta-section p { - font-size: 18px; - color: #333; - margin-bottom: 20px; +.hvac-google-map { + width: 100%; + height: 100%; + background: #f0f0f0; +} + +/* Map Loading State */ +.hvac-map-loading { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + color: var(--hvac-text-muted); +} + +.hvac-map-loading .dashicons { + font-size: 48px; + width: 48px; + height: 48px; + margin-bottom: 15px; + color: var(--hvac-primary); +} + +/* Map Error State */ +.hvac-map-error { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + color: var(--hvac-text-muted); + text-align: center; + padding: 30px; +} + +.hvac-map-error .dashicons { + font-size: 48px; + width: 48px; + height: 48px; + margin-bottom: 15px; + color: #e57373; +} + +.hvac-map-error a { + color: var(--hvac-primary); +} + +/* Map Legend Overlay */ +.hvac-map-legend { + position: absolute; + bottom: 24px; + left: 12px; + display: flex; + gap: 16px; + background: #fff; + padding: 10px 14px; + border-radius: 6px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); + font-size: 13px; + z-index: 10; +} + +.hvac-legend-item { + display: flex; + align-items: center; + gap: 6px; + color: var(--hvac-text); +} + +.hvac-legend-marker { + width: 14px; + height: 14px; + border-radius: 50%; +} + +.hvac-legend-trainer { + background: var(--hvac-primary); +} + +.hvac-legend-venue { + background: var(--hvac-venue-color); +} + +/* Map Toggles Overlay */ +.hvac-map-toggles { + position: absolute; + top: 12px; + left: 12px; + display: flex; + gap: 12px; + background: #fff; + padding: 8px 12px; + border-radius: 6px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); + z-index: 10; +} + +.hvac-toggle-compact { + display: flex; + align-items: center; + gap: 6px; + cursor: pointer; + font-size: 13px; + color: var(--hvac-text); +} + +.hvac-toggle-compact input { + width: 16px; + height: 16px; + accent-color: var(--hvac-primary); } /* ========================================================================== @@ -500,31 +675,32 @@ .hvac-btn-primary { display: inline-block; - padding: 14px 28px; - background: #00b3a4; + padding: 12px 24px; + background: var(--hvac-primary); color: #fff; border: none; border-radius: 6px; - font-size: 16px; + font-size: 15px; font-weight: 600; text-decoration: none; cursor: pointer; transition: background 0.2s; + text-align: center; } .hvac-btn-primary:hover { - background: #009688; + background: var(--hvac-primary-dark); color: #fff; } .hvac-btn-secondary { display: inline-block; - padding: 12px 24px; + padding: 10px 20px; background: #fff; - color: #164B60; - border: 2px solid #164B60; + color: var(--hvac-secondary); + border: 2px solid var(--hvac-secondary); border-radius: 6px; - font-size: 15px; + font-size: 14px; font-weight: 600; text-decoration: none; cursor: pointer; @@ -532,7 +708,7 @@ } .hvac-btn-secondary:hover { - background: #164B60; + background: var(--hvac-secondary); color: #fff; } @@ -576,7 +752,7 @@ .hvac-modal-loading { padding: 60px; text-align: center; - color: #666; + color: var(--hvac-text-muted); } .hvac-modal-close { @@ -606,7 +782,7 @@ } .hvac-training-modal-header h2 { - color: #164B60; + color: var(--hvac-secondary); font-size: 1.5rem; margin: 0; padding-right: 40px; @@ -645,7 +821,7 @@ .hvac-training-profile-avatar .dashicons { font-size: 48px; - color: #00b3a4; + color: var(--hvac-primary); } .hvac-training-profile-info { @@ -653,7 +829,7 @@ } .hvac-training-location { - color: #666; + color: var(--hvac-text-muted); margin-bottom: 8px; } @@ -670,7 +846,7 @@ } .hvac-training-events-count { - color: #333; + color: var(--hvac-text); } .hvac-training-detail { @@ -683,7 +859,7 @@ } .hvac-training-events h4 { - color: #164B60; + color: var(--hvac-secondary); margin-bottom: 12px; } @@ -703,7 +879,7 @@ } .hvac-events-list a { - color: #00b3a4; + color: var(--hvac-primary); text-decoration: none; } @@ -714,7 +890,7 @@ .hvac-event-date { display: block; font-size: 13px; - color: #666; + color: var(--hvac-text-muted); margin-top: 2px; } @@ -725,7 +901,7 @@ } .hvac-training-contact-section h4 { - color: #164B60; + color: var(--hvac-secondary); margin-bottom: 16px; } @@ -752,7 +928,7 @@ .hvac-training-contact-form input:focus, .hvac-training-contact-form textarea:focus { outline: none; - border-color: #00b3a4; + border-color: var(--hvac-primary); } .hvac-form-message { @@ -777,7 +953,7 @@ } .hvac-venue-modal-header h2 { - color: #164B60; + color: var(--hvac-secondary); font-size: 1.5rem; margin: 0; padding-right: 40px; @@ -788,12 +964,12 @@ } .hvac-venue-address { - color: #666; + color: var(--hvac-text-muted); margin-bottom: 20px; } .hvac-venue-events h4 { - color: #164B60; + color: var(--hvac-secondary); margin-bottom: 12px; } @@ -825,13 +1001,13 @@ .hvac-info-window-title { font-weight: 600; - color: #164B60; + color: var(--hvac-secondary); margin-bottom: 6px; font-size: 15px; } .hvac-info-window-location { - color: #666; + color: var(--hvac-text-muted); font-size: 13px; margin-bottom: 8px; } @@ -849,7 +1025,7 @@ .hvac-info-window-btn { display: inline-block; padding: 8px 16px; - background: #00b3a4; + background: var(--hvac-primary); color: #fff; border: none; border-radius: 4px; @@ -860,7 +1036,7 @@ } .hvac-info-window-btn:hover { - background: #009688; + background: var(--hvac-primary-dark); } /* Venue Info Window */ @@ -870,7 +1046,7 @@ } .hvac-info-window-address { - color: #666; + color: var(--hvac-text-muted); font-size: 13px; margin-bottom: 8px; } @@ -881,59 +1057,6 @@ margin-bottom: 10px; } -/* ========================================================================== - Responsive Styles - ========================================================================== */ - -@media (max-width: 768px) { - .hvac-find-training-page { - padding: 20px 0 40px; - } - - .hvac-find-training-page .hvac-page-title { - font-size: 1.75rem; - } - - .hvac-google-map { - height: 350px; - } - - .hvac-trainer-card { - flex-direction: column; - align-items: center; - text-align: center; - } - - .hvac-trainer-card-certs { - justify-content: center; - } - - .hvac-training-profile-header { - flex-direction: column; - align-items: center; - text-align: center; - } - - .hvac-training-contact-form .hvac-form-row { - grid-template-columns: 1fr; - } - - .hvac-modal-content { - max-height: 85vh; - } -} - -@media (max-width: 480px) { - .hvac-map-legend { - flex-direction: column; - gap: 10px; - } - - .hvac-trainer-grid { - grid-template-columns: 1fr; - } -} - /* ========================================================================== Location Error Message ========================================================================== */ @@ -971,3 +1094,417 @@ .hvac-location-error .hvac-dismiss-error:hover { opacity: 1; } + +/* ========================================================================== + Tablet Responsive (768px - 991px) + ========================================================================== */ + +@media (max-width: 991px) { + :root { + --hvac-sidebar-width: 320px; + } + + /* Switch to stacked layout - map on top, sidebar below */ + .hvac-map-layout { + grid-template-areas: + "map" + "sidebar" !important; + grid-template-columns: 1fr !important; + grid-template-rows: 55vh 1fr !important; + } + + .hvac-sidebar { + border-right: none; + border-top: 1px solid var(--hvac-border); + max-height: 45vh; + } + + /* Show sidebar toggle on tablet/mobile */ + .hvac-sidebar-toggle { + display: flex !important; + } + + /* Collapsible sidebar */ + .hvac-sidebar.collapsed { + max-height: 52px; + overflow: hidden; + } + + .hvac-sidebar.collapsed .hvac-sidebar-content { + display: none; + } + + /* Hide filter dropdowns, show toggle */ + .hvac-filter-dropdowns { + display: none !important; + } + + .hvac-mobile-filter-toggle { + display: flex !important; + } + + /* Override sidebar width for tablet/mobile */ + .hvac-sidebar { + width: 100% !important; + } + + /* Adjust map overlays */ + .hvac-map-legend { + bottom: 12px; + left: 8px; + padding: 8px 10px; + font-size: 12px; + } + + .hvac-map-toggles { + top: 8px; + left: 8px; + padding: 6px 10px; + font-size: 12px; + } +} + +/* ========================================================================== + Mobile Responsive (<768px) + ========================================================================== */ + +@media (max-width: 767px) { + .hvac-map-layout { + grid-template-rows: 50vh 1fr; + } + + .hvac-sidebar { + max-height: 50vh; + } + + /* Filter bar adjustments */ + .hvac-filter-bar { + padding: 10px 12px; + } + + .hvac-filter-bar-inner { + gap: 8px; + } + + .hvac-filter-search { + min-width: 0; + max-width: none; + flex: 1; + } + + .hvac-filter-search input { + padding: 8px 10px 8px 34px; + font-size: 14px; + } + + .hvac-near-me-btn { + padding: 8px 12px; + } + + .hvac-near-me-btn .hvac-btn-text { + display: none; + } + + .hvac-mobile-filter-toggle { + padding: 8px 12px; + } + + .hvac-mobile-filter-toggle .hvac-btn-text { + display: none; + } + + .hvac-clear-filters { + padding: 8px 12px; + font-size: 13px; + } + + /* Sidebar adjustments */ + .hvac-sidebar-header { + padding: 12px 14px; + } + + .hvac-sidebar-content { + padding: 12px; + } + + .hvac-trainer-card { + padding: 12px; + } + + .hvac-trainer-card-image { + width: 48px; + height: 48px; + } + + .hvac-trainer-card-name { + font-size: 14px; + } + + .hvac-trainer-card-location { + font-size: 12px; + } + + /* Modal adjustments */ + .hvac-modal-content { + max-height: 85vh; + border-radius: 12px 12px 0 0; + margin-top: auto; + } + + .hvac-training-profile-header { + flex-direction: column; + align-items: center; + text-align: center; + } + + .hvac-training-contact-form .hvac-form-row { + grid-template-columns: 1fr; + } +} + +/* ========================================================================== + Small Mobile (<480px) + ========================================================================== */ + +@media (max-width: 480px) { + .hvac-map-layout { + grid-template-rows: 45vh 1fr; + } + + .hvac-sidebar { + max-height: 55vh; + } + + .hvac-map-legend { + flex-direction: column; + gap: 6px; + bottom: 8px; + left: 6px; + padding: 6px 10px; + } + + .hvac-map-toggles { + flex-direction: column; + gap: 4px; + top: 6px; + left: 6px; + padding: 6px 10px; + } +} + +/* ========================================================================== + Equipment & Amenities Badges + ========================================================================== */ + +.hvac-badge-list { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 16px; +} + +.hvac-badge { + display: inline-block; + padding: 4px 12px; + border-radius: 16px; + font-size: 13px; + font-weight: 500; +} + +.hvac-badge-equipment { + background: #e6f7f5; + color: #00736a; + border: 1px solid #00a89d; +} + +.hvac-badge-amenity { + background: #f5f5f5; + color: #555; + border: 1px solid #ddd; +} + +/* ========================================================================== + Enhanced Venue Modal + ========================================================================== */ + +.hvac-venue-modal-content { + max-width: 600px; +} + +.hvac-venue-phone, +.hvac-venue-capacity { + color: var(--hvac-text); + margin-bottom: 8px; + font-size: 14px; +} + +.hvac-venue-description { + margin-bottom: 20px; + padding: 16px; + background: #f9fafb; + border-radius: 8px; + font-size: 14px; + line-height: 1.6; + color: var(--hvac-text); +} + +.hvac-venue-equipment, +.hvac-venue-amenities { + margin-bottom: 20px; +} + +.hvac-venue-equipment h4, +.hvac-venue-amenities h4 { + font-size: 14px; + font-weight: 600; + color: var(--hvac-secondary); + margin-bottom: 10px; +} + +.hvac-venue-actions { + margin-bottom: 24px; +} + +.hvac-venue-no-events { + color: var(--hvac-text-muted); + font-style: italic; + margin-bottom: 20px; +} + +/* ========================================================================== + Venue Contact Form + ========================================================================== */ + +.hvac-venue-contact-section { + border-top: 1px solid #eee; + padding-top: 24px; + margin-top: 24px; +} + +.hvac-venue-contact-section h4 { + color: var(--hvac-secondary); + margin-bottom: 16px; + font-size: 16px; +} + +.hvac-venue-contact-form .hvac-form-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; + margin-bottom: 12px; +} + +.hvac-venue-contact-form .hvac-form-group { + margin-bottom: 12px; +} + +.hvac-venue-contact-form label { + display: block; + font-size: 13px; + font-weight: 500; + color: var(--hvac-text); + margin-bottom: 4px; +} + +.hvac-venue-contact-form label .required { + color: #dc2626; +} + +.hvac-venue-contact-form input, +.hvac-venue-contact-form textarea { + width: 100%; + padding: 10px 12px; + font-size: 14px; + border: 1px solid #ddd; + border-radius: 6px; + background: #fff; + transition: border-color 0.2s; +} + +.hvac-venue-contact-form input:focus, +.hvac-venue-contact-form textarea:focus { + outline: none; + border-color: var(--hvac-primary); + box-shadow: 0 0 0 3px rgba(0, 168, 157, 0.1); +} + +.hvac-venue-contact-form textarea { + resize: vertical; + min-height: 80px; +} + +.hvac-venue-contact-form button[type="submit"] { + width: 100%; + margin-top: 8px; +} + +.hvac-venue-contact-section .hvac-form-success, +.hvac-venue-contact-section .hvac-form-error { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 16px; + border-radius: 8px; + margin-top: 16px; +} + +.hvac-venue-contact-section .hvac-form-success { + background: #d1fae5; + color: #065f46; +} + +.hvac-venue-contact-section .hvac-form-error { + background: #fee2e2; + color: #991b1b; +} + +.hvac-venue-contact-section .hvac-form-success .dashicons, +.hvac-venue-contact-section .hvac-form-error .dashicons { + font-size: 24px; + margin-top: 2px; +} + +.hvac-venue-contact-section .hvac-form-success p, +.hvac-venue-contact-section .hvac-form-error p { + margin: 0; + font-size: 14px; +} + +/* Responsive venue contact form */ +@media (max-width: 600px) { + .hvac-venue-contact-form .hvac-form-row { + grid-template-columns: 1fr; + } +} + +/* ========================================================================== + Print Styles + ========================================================================== */ + +@media print { + .hvac-find-training-page { + height: auto; + overflow: visible; + } + + .hvac-filter-bar, + .hvac-map-container, + .hvac-sidebar-toggle, + .hvac-load-more-wrapper, + .hvac-sidebar-cta { + display: none; + } + + .hvac-map-layout { + display: block; + } + + .hvac-sidebar { + max-height: none; + border: none; + } + + .hvac-trainer-card { + break-inside: avoid; + } +} diff --git a/assets/js/find-training-map.js b/assets/js/find-training-map.js index 47ee79a0..76848415 100644 --- a/assets/js/find-training-map.js +++ b/assets/js/find-training-map.js @@ -607,17 +607,68 @@ * Populate venue modal with data */ populateVenueModal: function(venue) { + const self = this; const $modal = $('#hvac-venue-modal'); // Title $modal.find('#venue-modal-title').text(venue.name); // Address - const addressParts = [venue.address, venue.city, venue.state].filter(Boolean); + const addressParts = [venue.address, venue.city, venue.state, venue.zip].filter(Boolean); $modal.find('.hvac-venue-address').text(addressParts.join(', ')); + // Phone + if (venue.phone) { + $modal.find('.hvac-venue-phone').html('Phone: ' + this.escapeHtml(venue.phone)).show(); + } else { + $modal.find('.hvac-venue-phone').hide(); + } + + // Capacity + if (venue.capacity) { + $modal.find('.hvac-venue-capacity').html('Capacity: ' + this.escapeHtml(venue.capacity)).show(); + } else { + $modal.find('.hvac-venue-capacity').hide(); + } + + // Description + if (venue.description) { + $modal.find('.hvac-venue-description').html(venue.description).show(); + } else { + $modal.find('.hvac-venue-description').hide(); + } + + // Equipment badges + const $equipmentSection = $modal.find('.hvac-venue-equipment'); + const $equipmentBadges = $modal.find('.hvac-equipment-badges'); + $equipmentBadges.empty(); + + if (venue.equipment && venue.equipment.length > 0) { + venue.equipment.forEach(item => { + $equipmentBadges.append(`${this.escapeHtml(item)}`); + }); + $equipmentSection.show(); + } else { + $equipmentSection.hide(); + } + + // Amenities badges + const $amenitiesSection = $modal.find('.hvac-venue-amenities'); + const $amenitiesBadges = $modal.find('.hvac-amenities-badges'); + $amenitiesBadges.empty(); + + if (venue.amenities && venue.amenities.length > 0) { + venue.amenities.forEach(item => { + $amenitiesBadges.append(`${this.escapeHtml(item)}`); + }); + $amenitiesSection.show(); + } else { + $amenitiesSection.hide(); + } + // Events list const $eventsList = $modal.find('.hvac-venue-events-list'); + const $noEvents = $modal.find('.hvac-venue-no-events'); $eventsList.empty(); if (venue.events && venue.events.length > 0) { @@ -629,13 +680,87 @@ `); }); + $eventsList.show(); + $noEvents.hide(); } else { - $eventsList.html('
  • No upcoming events at this venue.
  • '); + $eventsList.hide(); + $noEvents.show(); } // Directions link const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${venue.lat},${venue.lng}`; $modal.find('.hvac-venue-directions').attr('href', directionsUrl); + + // Set up contact form + const $form = $modal.find('.hvac-venue-contact-form'); + $form.data('venue-id', venue.id); + $form.attr('data-venue-id', venue.id); + $form.show(); + $modal.find('.hvac-form-success').hide(); + $modal.find('.hvac-form-error').hide(); + $form[0].reset(); + + // Bind contact form submission + this.bindVenueContactForm(); + }, + + /** + * Bind venue contact form submission + */ + bindVenueContactForm: function() { + const self = this; + + $('.hvac-venue-contact-form').off('submit').on('submit', function(e) { + e.preventDefault(); + self.submitVenueContactForm($(this)); + }); + }, + + /** + * Submit venue contact form + */ + submitVenueContactForm: function($form) { + const venueId = $form.data('venue-id'); + const $successMsg = $form.siblings('.hvac-form-success'); + const $errorMsg = $form.siblings('.hvac-form-error'); + const $submit = $form.find('button[type="submit"]'); + + // Collect form data + const formData = { + action: 'hvac_submit_venue_contact', + nonce: hvacFindTraining.nonce, + venue_id: venueId + }; + + $form.serializeArray().forEach(field => { + formData[field.name] = field.value; + }); + + // Submit + $submit.prop('disabled', true).text('Sending...'); + $successMsg.hide(); + $errorMsg.hide(); + + $.ajax({ + url: hvacFindTraining.ajax_url, + type: 'POST', + data: formData, + success: function(response) { + if (response.success) { + $form.hide(); + $successMsg.show(); + } else { + $errorMsg.find('p').text(response.data?.message || 'There was a problem sending your message.'); + $errorMsg.show(); + } + }, + error: function() { + $errorMsg.show(); + }, + complete: function() { + $submit.prop('disabled', false).text('Send Message'); + } + }); }, /** diff --git a/includes/class-hvac-ajax-handlers.php b/includes/class-hvac-ajax-handlers.php index be3bcd5c..c5b402fc 100644 --- a/includes/class-hvac-ajax-handlers.php +++ b/includes/class-hvac-ajax-handlers.php @@ -69,6 +69,10 @@ class HVAC_Ajax_Handlers { // Contact trainer form (Find Training page) add_action('wp_ajax_hvac_submit_contact_form', array($this, 'submit_trainer_contact_form')); add_action('wp_ajax_nopriv_hvac_submit_contact_form', array($this, 'submit_trainer_contact_form')); + + // Contact venue form (Find Training page - Approved Labs) + add_action('wp_ajax_hvac_submit_venue_contact', array($this, 'submit_venue_contact_form')); + add_action('wp_ajax_nopriv_hvac_submit_venue_contact', array($this, 'submit_venue_contact_form')); } /** @@ -1185,6 +1189,177 @@ class HVAC_Ajax_Handlers { ]); } + /** + * Handle venue contact form submission from Find Training page + * + * Sends an email to the venue POC (Point of Contact) with the visitor's inquiry. + * Available to both logged-in and anonymous users. + */ + public function submit_venue_contact_form() { + // Verify nonce + if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_find_training')) { + wp_send_json_error(['message' => 'Invalid security token'], 403); + return; + } + + // Rate limiting - max 5 submissions per IP per hour + $ip = $this->get_client_ip(); + $rate_key = 'hvac_venue_contact_rate_' . md5($ip); + $submissions = get_transient($rate_key) ?: 0; + + if ($submissions >= 5) { + wp_send_json_error(['message' => 'Too many submissions. Please try again later.'], 429); + return; + } + + // Validate required fields + $required_fields = ['first_name', 'last_name', 'email', 'venue_id']; + foreach ($required_fields as $field) { + if (empty($_POST[$field])) { + wp_send_json_error(['message' => "Missing required field: {$field}"], 400); + return; + } + } + + // Sanitize inputs + $first_name = sanitize_text_field($_POST['first_name']); + $last_name = sanitize_text_field($_POST['last_name']); + $email = sanitize_email($_POST['email']); + $phone = sanitize_text_field($_POST['phone'] ?? ''); + $company = sanitize_text_field($_POST['company'] ?? ''); + $message = sanitize_textarea_field($_POST['message'] ?? ''); + $venue_id = absint($_POST['venue_id']); + + // Validate email + if (!is_email($email)) { + wp_send_json_error(['message' => 'Invalid email address'], 400); + return; + } + + // Get venue data + $venue = get_post($venue_id); + if (!$venue || $venue->post_type !== 'tribe_venue') { + wp_send_json_error(['message' => 'Venue not found'], 404); + return; + } + + $venue_name = $venue->post_title; + + // Get POC information from venue meta + $poc_user_id = get_post_meta($venue_id, '_venue_poc_user_id', true); + $poc_email = get_post_meta($venue_id, '_venue_poc_email', true); + $poc_name = get_post_meta($venue_id, '_venue_poc_name', true); + + // Fallback to post author if no POC meta + if (empty($poc_user_id)) { + $poc_user_id = $venue->post_author; + } + + if (empty($poc_email)) { + $author = get_userdata($poc_user_id); + if ($author) { + $poc_email = $author->user_email; + $poc_name = $poc_name ?: $author->display_name; + } + } + + if (empty($poc_email)) { + wp_send_json_error(['message' => 'Unable to find contact for this venue'], 500); + return; + } + + // Build email content + $subject = sprintf( + '[Upskill HVAC] Training Lab Inquiry - %s', + $venue_name + ); + + $body = sprintf( + "Hello %s,\n\n" . + "You have received an inquiry about your training lab through the Upskill HVAC directory.\n\n" . + "--- Training Lab ---\n" . + "%s\n\n" . + "--- Contact Details ---\n" . + "Name: %s %s\n" . + "Email: %s\n" . + "%s" . // Phone (optional) + "%s" . // Company (optional) + "\n--- Message ---\n%s\n\n" . + "---\n" . + "This message was sent via the Find Training page at %s\n" . + "Please respond directly to the sender's email address.\n", + $poc_name ?: 'Training Lab Contact', + $venue_name, + $first_name, + $last_name, + $email, + $phone ? "Phone: {$phone}\n" : '', + $company ? "Company: {$company}\n" : '', + $message ?: '(No message provided)', + home_url('/find-training/') + ); + + // Email headers + $headers = [ + 'Content-Type: text/plain; charset=UTF-8', + sprintf('Reply-To: %s %s <%s>', $first_name, $last_name, $email), + sprintf('From: Upskill HVAC ', parse_url(home_url(), PHP_URL_HOST)) + ]; + + // Send email to POC + $sent = wp_mail($poc_email, $subject, $body, $headers); + + if (!$sent) { + // Log failure + if (class_exists('HVAC_Logger')) { + HVAC_Logger::error('Failed to send venue contact email', 'AJAX', [ + 'venue_id' => $venue_id, + 'poc_email' => $poc_email, + 'sender_email' => $email + ]); + } + wp_send_json_error(['message' => 'Failed to send message. Please try again.'], 500); + return; + } + + // Update rate limit + set_transient($rate_key, $submissions + 1, HOUR_IN_SECONDS); + + // Log success + if (class_exists('HVAC_Logger')) { + HVAC_Logger::info('Venue contact form submitted', 'AJAX', [ + 'venue_id' => $venue_id, + 'venue_name' => $venue_name, + 'poc_email' => $poc_email, + 'sender_email' => $email, + 'has_message' => !empty($message) + ]); + } + + // Store lead if training leads system exists + if (class_exists('HVAC_Training_Leads')) { + $leads = HVAC_Training_Leads::instance(); + if (method_exists($leads, 'create_lead')) { + $leads->create_lead([ + 'first_name' => $first_name, + 'last_name' => $last_name, + 'email' => $email, + 'phone' => $phone, + 'company' => $company, + 'message' => $message, + 'venue_id' => $venue_id, + 'venue_name' => $venue_name, + 'source' => 'find_training_venue_contact' + ]); + } + } + + wp_send_json_success([ + 'message' => 'Your message has been sent to the training lab.', + 'venue_name' => $venue_name + ]); + } + /** * Get client IP address safely * diff --git a/includes/class-hvac-plugin.php b/includes/class-hvac-plugin.php index 3ed8fe06..d1fda656 100644 --- a/includes/class-hvac-plugin.php +++ b/includes/class-hvac-plugin.php @@ -270,6 +270,9 @@ final class HVAC_Plugin { 'find-training/class-hvac-training-map-data.php', 'find-training/class-hvac-venue-geocoding.php', ]; + + // Venue Categories (taxonomies for training labs) + require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-venue-categories.php'; // Load feature files with memory-efficient generator foreach ($this->loadFeatureFiles($featureFiles) as $file => $status) { @@ -592,6 +595,11 @@ final class HVAC_Plugin { if (class_exists('HVAC_Venues')) { HVAC_Venues::instance(); } + + // Initialize venue categories (taxonomies for training labs) + if (class_exists('HVAC_Venue_Categories')) { + HVAC_Venue_Categories::instance(); + } // Initialize trainer profile manager if (class_exists('HVAC_Trainer_Profile_Manager')) { diff --git a/includes/class-hvac-venue-categories.php b/includes/class-hvac-venue-categories.php new file mode 100644 index 00000000..b5180f78 --- /dev/null +++ b/includes/class-hvac-venue-categories.php @@ -0,0 +1,282 @@ + [ + 'name' => __('Venue Types', 'hvac-community-events'), + 'singular_name' => __('Venue Type', 'hvac-community-events'), + 'search_items' => __('Search Venue Types', 'hvac-community-events'), + 'all_items' => __('All Venue Types', 'hvac-community-events'), + 'parent_item' => __('Parent Venue Type', 'hvac-community-events'), + 'parent_item_colon' => __('Parent Venue Type:', 'hvac-community-events'), + 'edit_item' => __('Edit Venue Type', 'hvac-community-events'), + 'update_item' => __('Update Venue Type', 'hvac-community-events'), + 'add_new_item' => __('Add New Venue Type', 'hvac-community-events'), + 'new_item_name' => __('New Venue Type Name', 'hvac-community-events'), + 'menu_name' => __('Venue Types', 'hvac-community-events'), + ], + 'hierarchical' => true, + 'public' => true, + 'show_ui' => true, + 'show_in_menu' => true, + 'show_admin_column' => true, + 'show_in_rest' => true, + 'query_var' => true, + 'rewrite' => ['slug' => 'venue-type'], + ]); + + // Venue Equipment taxonomy + register_taxonomy('venue_equipment', 'tribe_venue', [ + 'labels' => [ + 'name' => __('Equipment', 'hvac-community-events'), + 'singular_name' => __('Equipment', 'hvac-community-events'), + 'search_items' => __('Search Equipment', 'hvac-community-events'), + 'all_items' => __('All Equipment', 'hvac-community-events'), + 'parent_item' => __('Parent Equipment', 'hvac-community-events'), + 'parent_item_colon' => __('Parent Equipment:', 'hvac-community-events'), + 'edit_item' => __('Edit Equipment', 'hvac-community-events'), + 'update_item' => __('Update Equipment', 'hvac-community-events'), + 'add_new_item' => __('Add New Equipment', 'hvac-community-events'), + 'new_item_name' => __('New Equipment Name', 'hvac-community-events'), + 'menu_name' => __('Equipment', 'hvac-community-events'), + ], + 'hierarchical' => true, + 'public' => true, + 'show_ui' => true, + 'show_in_menu' => true, + 'show_admin_column' => true, + 'show_in_rest' => true, + 'query_var' => true, + 'rewrite' => ['slug' => 'venue-equipment'], + ]); + + // Venue Amenities taxonomy + register_taxonomy('venue_amenities', 'tribe_venue', [ + 'labels' => [ + 'name' => __('Amenities', 'hvac-community-events'), + 'singular_name' => __('Amenity', 'hvac-community-events'), + 'search_items' => __('Search Amenities', 'hvac-community-events'), + 'all_items' => __('All Amenities', 'hvac-community-events'), + 'parent_item' => __('Parent Amenity', 'hvac-community-events'), + 'parent_item_colon' => __('Parent Amenity:', 'hvac-community-events'), + 'edit_item' => __('Edit Amenity', 'hvac-community-events'), + 'update_item' => __('Update Amenity', 'hvac-community-events'), + 'add_new_item' => __('Add New Amenity', 'hvac-community-events'), + 'new_item_name' => __('New Amenity Name', 'hvac-community-events'), + 'menu_name' => __('Amenities', 'hvac-community-events'), + ], + 'hierarchical' => true, + 'public' => true, + 'show_ui' => true, + 'show_in_menu' => true, + 'show_admin_column' => true, + 'show_in_rest' => true, + 'query_var' => true, + 'rewrite' => ['slug' => 'venue-amenities'], + ]); + } + + /** + * Create default taxonomy terms + */ + public function create_default_terms(): void { + // Only run once + if (get_option('hvac_venue_categories_initialized')) { + return; + } + + // Venue Types + $venue_types = [ + 'measureQuick Approved Training Lab' => 'mq-approved-lab', + ]; + + foreach ($venue_types as $name => $slug) { + if (!term_exists($slug, 'venue_type')) { + wp_insert_term($name, 'venue_type', ['slug' => $slug]); + } + } + + // Equipment + $equipment = [ + 'Furnace', + 'Heat Pump', + 'Air Conditioner', + 'Mini-Split', + 'Boiler', + 'Gas Meter', + 'TrueFlow Grid', + 'Flow Hood', + ]; + + foreach ($equipment as $name) { + $slug = sanitize_title($name); + if (!term_exists($slug, 'venue_equipment')) { + wp_insert_term($name, 'venue_equipment', ['slug' => $slug]); + } + } + + // Amenities + $amenities = [ + 'Coffee', + 'Water', + 'Tea', + 'Soda', + 'Snacks', + 'Projector', + 'Whiteboard', + 'WiFi', + 'Parking', + 'Vending Machines', + ]; + + foreach ($amenities as $name) { + $slug = sanitize_title($name); + if (!term_exists($slug, 'venue_amenities')) { + wp_insert_term($name, 'venue_amenities', ['slug' => $slug]); + } + } + + update_option('hvac_venue_categories_initialized', true); + } + + /** + * Get venues by type + * + * @param string $type_slug Type slug (e.g., 'mq-approved-lab') + * @return array Venue IDs + */ + public function get_venues_by_type(string $type_slug): array { + $query = new WP_Query([ + 'post_type' => 'tribe_venue', + 'posts_per_page' => -1, + 'post_status' => 'publish', + 'fields' => 'ids', + 'tax_query' => [ + [ + 'taxonomy' => 'venue_type', + 'field' => 'slug', + 'terms' => $type_slug, + ], + ], + ]); + + return $query->posts; + } + + /** + * Check if venue is an approved training lab + * + * @param int $venue_id Venue post ID + * @return bool + */ + public function is_approved_training_lab(int $venue_id): bool { + return has_term('mq-approved-lab', 'venue_type', $venue_id); + } + + /** + * Get venue equipment + * + * @param int $venue_id Venue post ID + * @return array Equipment names + */ + public function get_venue_equipment(int $venue_id): array { + $terms = wp_get_post_terms($venue_id, 'venue_equipment', ['fields' => 'names']); + return is_wp_error($terms) ? [] : $terms; + } + + /** + * Get venue amenities + * + * @param int $venue_id Venue post ID + * @return array Amenity names + */ + public function get_venue_amenities(int $venue_id): array { + $terms = wp_get_post_terms($venue_id, 'venue_amenities', ['fields' => 'names']); + return is_wp_error($terms) ? [] : $terms; + } + + /** + * Set venue as approved training lab + * + * @param int $venue_id Venue post ID + * @return bool|WP_Error + */ + public function set_as_approved_lab(int $venue_id) { + return wp_set_post_terms($venue_id, ['mq-approved-lab'], 'venue_type', false); + } + + /** + * Set venue equipment + * + * @param int $venue_id Venue post ID + * @param array $equipment Equipment slugs or names + * @return bool|WP_Error + */ + public function set_venue_equipment(int $venue_id, array $equipment) { + return wp_set_post_terms($venue_id, $equipment, 'venue_equipment', false); + } + + /** + * Set venue amenities + * + * @param int $venue_id Venue post ID + * @param array $amenities Amenity slugs or names + * @return bool|WP_Error + */ + public function set_venue_amenities(int $venue_id, array $amenities) { + return wp_set_post_terms($venue_id, $amenities, 'venue_amenities', false); + } +} diff --git a/includes/find-training/class-hvac-training-map-data.php b/includes/find-training/class-hvac-training-map-data.php index 913f4457..ffa34aee 100644 --- a/includes/find-training/class-hvac-training-map-data.php +++ b/includes/find-training/class-hvac-training-map-data.php @@ -215,6 +215,8 @@ class HVAC_Training_Map_Data { /** * Get venue markers for map * + * Filters to show only measureQuick Approved Training Labs. + * * @param array $filters Optional filters * @return array Venue markers data */ @@ -225,18 +227,26 @@ class HVAC_Training_Map_Data { } // Generate cache key - $cache_key = 'venues_' . md5(serialize($filters)); + $cache_key = 'venues_approved_labs_' . md5(serialize($filters)); $cached = wp_cache_get($cache_key, $this->cache_group); if ($cached !== false && empty($filters)) { return $cached; } - // Build query args + // Build query args - filter for approved training labs only $query_args = [ 'post_type' => 'tribe_venue', 'posts_per_page' => -1, 'post_status' => 'publish', + // Only show venues tagged as measureQuick Approved Training Labs + 'tax_query' => [ + [ + 'taxonomy' => 'venue_type', + 'field' => 'slug', + 'terms' => 'mq-approved-lab', + ], + ], 'meta_query' => [ 'relation' => 'AND', [ @@ -508,6 +518,30 @@ class HVAC_Training_Map_Data { $data['country'] = get_post_meta($venue_id, '_VenueCountry', true); $data['phone'] = get_post_meta($venue_id, '_VenuePhone', true); $data['website'] = get_post_meta($venue_id, '_VenueURL', true); + $data['description'] = $venue->post_content; + $data['capacity'] = get_post_meta($venue_id, '_VenueCapacity', true); + + // Get equipment and amenities taxonomies + $equipment = wp_get_post_terms($venue_id, 'venue_equipment', ['fields' => 'names']); + $data['equipment'] = is_wp_error($equipment) ? [] : $equipment; + + $amenities = wp_get_post_terms($venue_id, 'venue_amenities', ['fields' => 'names']); + $data['amenities'] = is_wp_error($amenities) ? [] : $amenities; + + // Get POC (Point of Contact) information + $data['poc_user_id'] = get_post_meta($venue_id, '_venue_poc_user_id', true); + $data['poc_name'] = get_post_meta($venue_id, '_venue_poc_name', true); + $data['poc_email'] = get_post_meta($venue_id, '_venue_poc_email', true); + + // If no POC meta, use post author as fallback + if (empty($data['poc_user_id'])) { + $data['poc_user_id'] = $venue->post_author; + $author = get_userdata($venue->post_author); + if ($author) { + $data['poc_name'] = $author->display_name; + $data['poc_email'] = $author->user_email; + } + } // Get upcoming events list if (function_exists('tribe_get_events')) { diff --git a/templates/page-find-training.php b/templates/page-find-training.php index 1a7b8df7..4418000b 100644 --- a/templates/page-find-training.php +++ b/templates/page-find-training.php @@ -2,8 +2,8 @@ /** * Template Name: Find Training * - * Template for displaying the Find Training page with Google Maps - * showing trainers and venues on an interactive map. + * Google Maps-style full-screen layout with left sidebar panel + * for trainer directory and compact filter toolbar. * * @package HVAC_Community_Events * @since 2.2.0 @@ -17,149 +17,187 @@ get_header(); // Get page handler instance $find_training = HVAC_Find_Training_Page::get_instance(); $filter_options = $find_training->get_filter_options(); +$api_key_configured = $find_training->is_api_key_configured(); ?>
    -
    + + Skip to trainer results - -

    Find Training

    - - -
    -

    Upskill HVAC is proud to be the only training body offering Certified measureQuick training.

    -

    Certified measureQuick Trainers have demonstrated their skills and mastery of HVAC science and the measureQuick app, and are authorized to provide measureQuick training to the industry.

    -

    Use the interactive map and filters below to discover trainers and training venues near you. Click on any marker to view details.

    -
    - - -
    - - -
    -
    -
    - -

    Loading map...

    -
    -
    - - -
    -
    - - Trainer -
    -
    - - Training Venue -
    -
    + +