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 <noreply@anthropic.com>
This commit is contained in:
parent
19147d978e
commit
5c15b27935
8 changed files with 1859 additions and 518 deletions
81
Status.md
81
Status.md
|
|
@ -1,12 +1,87 @@
|
||||||
# HVAC Community Events - Project Status
|
# HVAC Community Events - Project Status
|
||||||
|
|
||||||
**Last Updated:** February 1, 2026
|
**Last Updated:** February 1, 2026
|
||||||
**Current Session:** Find Training Page Enhancements - Complete
|
**Current Session:** measureQuick Approved Training Labs Implementation
|
||||||
**Version:** 2.2.4 (Deployed to Production)
|
**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**
|
### Status: ✅ **COMPLETE - Deployed to Production**
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -607,17 +607,68 @@
|
||||||
* Populate venue modal with data
|
* Populate venue modal with data
|
||||||
*/
|
*/
|
||||||
populateVenueModal: function(venue) {
|
populateVenueModal: function(venue) {
|
||||||
|
const self = this;
|
||||||
const $modal = $('#hvac-venue-modal');
|
const $modal = $('#hvac-venue-modal');
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
$modal.find('#venue-modal-title').text(venue.name);
|
$modal.find('#venue-modal-title').text(venue.name);
|
||||||
|
|
||||||
// Address
|
// 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(', '));
|
$modal.find('.hvac-venue-address').text(addressParts.join(', '));
|
||||||
|
|
||||||
|
// Phone
|
||||||
|
if (venue.phone) {
|
||||||
|
$modal.find('.hvac-venue-phone').html('<strong>Phone:</strong> ' + this.escapeHtml(venue.phone)).show();
|
||||||
|
} else {
|
||||||
|
$modal.find('.hvac-venue-phone').hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capacity
|
||||||
|
if (venue.capacity) {
|
||||||
|
$modal.find('.hvac-venue-capacity').html('<strong>Capacity:</strong> ' + 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(`<span class="hvac-badge hvac-badge-equipment">${this.escapeHtml(item)}</span>`);
|
||||||
|
});
|
||||||
|
$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(`<span class="hvac-badge hvac-badge-amenity">${this.escapeHtml(item)}</span>`);
|
||||||
|
});
|
||||||
|
$amenitiesSection.show();
|
||||||
|
} else {
|
||||||
|
$amenitiesSection.hide();
|
||||||
|
}
|
||||||
|
|
||||||
// Events list
|
// Events list
|
||||||
const $eventsList = $modal.find('.hvac-venue-events-list');
|
const $eventsList = $modal.find('.hvac-venue-events-list');
|
||||||
|
const $noEvents = $modal.find('.hvac-venue-no-events');
|
||||||
$eventsList.empty();
|
$eventsList.empty();
|
||||||
|
|
||||||
if (venue.events && venue.events.length > 0) {
|
if (venue.events && venue.events.length > 0) {
|
||||||
|
|
@ -629,13 +680,87 @@
|
||||||
</li>
|
</li>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
$eventsList.show();
|
||||||
|
$noEvents.hide();
|
||||||
} else {
|
} else {
|
||||||
$eventsList.html('<li>No upcoming events at this venue.</li>');
|
$eventsList.hide();
|
||||||
|
$noEvents.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Directions link
|
// Directions link
|
||||||
const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${venue.lat},${venue.lng}`;
|
const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${venue.lat},${venue.lng}`;
|
||||||
$modal.find('.hvac-venue-directions').attr('href', directionsUrl);
|
$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');
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,10 @@ class HVAC_Ajax_Handlers {
|
||||||
// Contact trainer form (Find Training page)
|
// Contact trainer form (Find Training page)
|
||||||
add_action('wp_ajax_hvac_submit_contact_form', array($this, 'submit_trainer_contact_form'));
|
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'));
|
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 <noreply@%s>', 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
|
* Get client IP address safely
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -271,6 +271,9 @@ final class HVAC_Plugin {
|
||||||
'find-training/class-hvac-venue-geocoding.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
|
// Load feature files with memory-efficient generator
|
||||||
foreach ($this->loadFeatureFiles($featureFiles) as $file => $status) {
|
foreach ($this->loadFeatureFiles($featureFiles) as $file => $status) {
|
||||||
if ($status === 'loaded') {
|
if ($status === 'loaded') {
|
||||||
|
|
@ -593,6 +596,11 @@ final class HVAC_Plugin {
|
||||||
HVAC_Venues::instance();
|
HVAC_Venues::instance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize venue categories (taxonomies for training labs)
|
||||||
|
if (class_exists('HVAC_Venue_Categories')) {
|
||||||
|
HVAC_Venue_Categories::instance();
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize trainer profile manager
|
// Initialize trainer profile manager
|
||||||
if (class_exists('HVAC_Trainer_Profile_Manager')) {
|
if (class_exists('HVAC_Trainer_Profile_Manager')) {
|
||||||
HVAC_Trainer_Profile_Manager::get_instance();
|
HVAC_Trainer_Profile_Manager::get_instance();
|
||||||
|
|
|
||||||
282
includes/class-hvac-venue-categories.php
Normal file
282
includes/class-hvac-venue-categories.php
Normal file
|
|
@ -0,0 +1,282 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Venue Categories - Taxonomy Registration for Training Labs
|
||||||
|
*
|
||||||
|
* Registers custom taxonomies for TEC venues:
|
||||||
|
* - venue_type: Training lab types (e.g., "measureQuick Approved Training Lab")
|
||||||
|
* - venue_equipment: Equipment available at venues
|
||||||
|
* - venue_amenities: Amenities available at venues
|
||||||
|
*
|
||||||
|
* @package HVAC_Community_Events
|
||||||
|
* @since 2.3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class HVAC_Venue_Categories
|
||||||
|
*
|
||||||
|
* Manages venue taxonomies for categorizing training labs.
|
||||||
|
*/
|
||||||
|
class HVAC_Venue_Categories {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton instance
|
||||||
|
*
|
||||||
|
* @var HVAC_Venue_Categories|null
|
||||||
|
*/
|
||||||
|
private static ?self $instance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get singleton instance
|
||||||
|
*
|
||||||
|
* @return HVAC_Venue_Categories
|
||||||
|
*/
|
||||||
|
public static function instance(): self {
|
||||||
|
if (self::$instance === null) {
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
private function __construct() {
|
||||||
|
add_action('init', [$this, 'register_taxonomies'], 5);
|
||||||
|
add_action('init', [$this, 'create_default_terms'], 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register venue taxonomies
|
||||||
|
*/
|
||||||
|
public function register_taxonomies(): void {
|
||||||
|
// Venue Type taxonomy (e.g., "measureQuick Approved Training Lab")
|
||||||
|
register_taxonomy('venue_type', 'tribe_venue', [
|
||||||
|
'labels' => [
|
||||||
|
'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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -215,6 +215,8 @@ class HVAC_Training_Map_Data {
|
||||||
/**
|
/**
|
||||||
* Get venue markers for map
|
* Get venue markers for map
|
||||||
*
|
*
|
||||||
|
* Filters to show only measureQuick Approved Training Labs.
|
||||||
|
*
|
||||||
* @param array $filters Optional filters
|
* @param array $filters Optional filters
|
||||||
* @return array Venue markers data
|
* @return array Venue markers data
|
||||||
*/
|
*/
|
||||||
|
|
@ -225,18 +227,26 @@ class HVAC_Training_Map_Data {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate cache key
|
// 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);
|
$cached = wp_cache_get($cache_key, $this->cache_group);
|
||||||
|
|
||||||
if ($cached !== false && empty($filters)) {
|
if ($cached !== false && empty($filters)) {
|
||||||
return $cached;
|
return $cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build query args
|
// Build query args - filter for approved training labs only
|
||||||
$query_args = [
|
$query_args = [
|
||||||
'post_type' => 'tribe_venue',
|
'post_type' => 'tribe_venue',
|
||||||
'posts_per_page' => -1,
|
'posts_per_page' => -1,
|
||||||
'post_status' => 'publish',
|
'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' => [
|
'meta_query' => [
|
||||||
'relation' => 'AND',
|
'relation' => 'AND',
|
||||||
[
|
[
|
||||||
|
|
@ -508,6 +518,30 @@ class HVAC_Training_Map_Data {
|
||||||
$data['country'] = get_post_meta($venue_id, '_VenueCountry', true);
|
$data['country'] = get_post_meta($venue_id, '_VenueCountry', true);
|
||||||
$data['phone'] = get_post_meta($venue_id, '_VenuePhone', true);
|
$data['phone'] = get_post_meta($venue_id, '_VenuePhone', true);
|
||||||
$data['website'] = get_post_meta($venue_id, '_VenueURL', 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
|
// Get upcoming events list
|
||||||
if (function_exists('tribe_get_events')) {
|
if (function_exists('tribe_get_events')) {
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
/**
|
/**
|
||||||
* Template Name: Find Training
|
* Template Name: Find Training
|
||||||
*
|
*
|
||||||
* Template for displaying the Find Training page with Google Maps
|
* Google Maps-style full-screen layout with left sidebar panel
|
||||||
* showing trainers and venues on an interactive map.
|
* for trainer directory and compact filter toolbar.
|
||||||
*
|
*
|
||||||
* @package HVAC_Community_Events
|
* @package HVAC_Community_Events
|
||||||
* @since 2.2.0
|
* @since 2.2.0
|
||||||
|
|
@ -17,149 +17,187 @@ get_header();
|
||||||
// Get page handler instance
|
// Get page handler instance
|
||||||
$find_training = HVAC_Find_Training_Page::get_instance();
|
$find_training = HVAC_Find_Training_Page::get_instance();
|
||||||
$filter_options = $find_training->get_filter_options();
|
$filter_options = $find_training->get_filter_options();
|
||||||
|
$api_key_configured = $find_training->is_api_key_configured();
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="hvac-find-training-page">
|
<div class="hvac-find-training-page">
|
||||||
<div class="ast-container">
|
<!-- Skip link for accessibility -->
|
||||||
|
<a href="#hvac-trainer-grid" class="hvac-skip-link">Skip to trainer results</a>
|
||||||
|
|
||||||
<!-- Page Title -->
|
<!-- Compact Filter Bar -->
|
||||||
<h1 class="hvac-page-title">Find Training</h1>
|
<div class="hvac-filter-bar" role="search" aria-label="Filter trainers and venues">
|
||||||
|
<div class="hvac-filter-bar-inner">
|
||||||
<!-- Intro Section -->
|
<!-- Search -->
|
||||||
<div class="hvac-find-training-intro">
|
<div class="hvac-filter-item hvac-filter-search">
|
||||||
<p>Upskill HVAC is proud to be the only training body offering Certified measureQuick training.</p>
|
<span class="dashicons dashicons-search" aria-hidden="true"></span>
|
||||||
<p><strong>Certified measureQuick Trainers</strong> have demonstrated their skills and mastery of HVAC science and the measureQuick app, and are authorized to provide measureQuick training to the industry.</p>
|
<input type="text" id="hvac-training-search" placeholder="Search trainers..." aria-label="Search trainers">
|
||||||
<p>Use the interactive map and filters below to discover trainers and training venues near you. Click on any marker to view details.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Map and Filters Container -->
|
|
||||||
<div class="hvac-map-filters-wrapper">
|
|
||||||
|
|
||||||
<!-- Map Section -->
|
|
||||||
<div class="hvac-map-section">
|
|
||||||
<div id="hvac-training-map" class="hvac-google-map">
|
|
||||||
<div class="hvac-map-loading">
|
|
||||||
<span class="dashicons dashicons-location"></span>
|
|
||||||
<p>Loading map...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Map Legend -->
|
|
||||||
<div class="hvac-map-legend">
|
|
||||||
<div class="hvac-legend-item">
|
|
||||||
<span class="hvac-legend-marker hvac-legend-trainer"></span>
|
|
||||||
<span>Trainer</span>
|
|
||||||
</div>
|
|
||||||
<div class="hvac-legend-item">
|
|
||||||
<span class="hvac-legend-marker hvac-legend-venue"></span>
|
|
||||||
<span>Training Venue</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filters Section -->
|
<!-- Filter Dropdowns (hidden on mobile, shown in panel) -->
|
||||||
<div class="hvac-filters-section">
|
<div class="hvac-filter-dropdowns">
|
||||||
<!-- Search Box -->
|
<select id="hvac-filter-state" class="hvac-filter-select" aria-label="Filter by state">
|
||||||
<div class="hvac-search-box">
|
<option value="">All States</option>
|
||||||
<input type="text" id="hvac-training-search" class="hvac-search-input" placeholder="Search trainers or venues..." aria-label="Search">
|
<?php foreach ($filter_options['states'] as $state): ?>
|
||||||
<span class="dashicons dashicons-search"></span>
|
<option value="<?php echo esc_attr($state); ?>"><?php echo esc_html($state); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="hvac-filter-certification" class="hvac-filter-select" aria-label="Filter by certification">
|
||||||
|
<option value="">All Certifications</option>
|
||||||
|
<?php foreach ($filter_options['certifications'] as $cert): ?>
|
||||||
|
<option value="<?php echo esc_attr($cert); ?>"><?php echo esc_html($cert); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="hvac-filter-format" class="hvac-filter-select" aria-label="Filter by training format">
|
||||||
|
<option value="">All Formats</option>
|
||||||
|
<?php foreach ($filter_options['training_formats'] as $format): ?>
|
||||||
|
<option value="<?php echo esc_attr($format); ?>"><?php echo esc_html($format); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Near Me Button -->
|
||||||
|
<button type="button" id="hvac-near-me-btn" class="hvac-near-me-btn">
|
||||||
|
<span class="dashicons dashicons-location-alt" aria-hidden="true"></span>
|
||||||
|
<span class="hvac-btn-text">Near Me</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Clear Filters -->
|
||||||
|
<button type="button" class="hvac-clear-filters" style="display: none;">
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Mobile Filter Toggle -->
|
||||||
|
<button type="button"
|
||||||
|
class="hvac-mobile-filter-toggle"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="hvac-mobile-filter-panel">
|
||||||
|
<span class="dashicons dashicons-filter" aria-hidden="true"></span>
|
||||||
|
<span class="hvac-btn-text">Filters</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Active Filters Chips -->
|
||||||
|
<div class="hvac-active-filters" aria-live="polite"></div>
|
||||||
|
|
||||||
|
<!-- Mobile Filter Panel (hidden by default) -->
|
||||||
|
<div id="hvac-mobile-filter-panel" class="hvac-mobile-filter-panel" hidden>
|
||||||
|
<div class="hvac-mobile-filter-group">
|
||||||
|
<label for="hvac-filter-state-mobile">State / Province</label>
|
||||||
|
<select id="hvac-filter-state-mobile" class="hvac-filter-select" aria-label="Filter by state">
|
||||||
|
<option value="">All States</option>
|
||||||
|
<?php foreach ($filter_options['states'] as $state): ?>
|
||||||
|
<option value="<?php echo esc_attr($state); ?>"><?php echo esc_html($state); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="hvac-mobile-filter-group">
|
||||||
|
<label for="hvac-filter-certification-mobile">Certification</label>
|
||||||
|
<select id="hvac-filter-certification-mobile" class="hvac-filter-select" aria-label="Filter by certification">
|
||||||
|
<option value="">All Certifications</option>
|
||||||
|
<?php foreach ($filter_options['certifications'] as $cert): ?>
|
||||||
|
<option value="<?php echo esc_attr($cert); ?>"><?php echo esc_html($cert); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="hvac-mobile-filter-group">
|
||||||
|
<label for="hvac-filter-format-mobile">Training Format</label>
|
||||||
|
<select id="hvac-filter-format-mobile" class="hvac-filter-select" aria-label="Filter by training format">
|
||||||
|
<option value="">All Formats</option>
|
||||||
|
<?php foreach ($filter_options['training_formats'] as $format): ?>
|
||||||
|
<option value="<?php echo esc_attr($format); ?>"><?php echo esc_html($format); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Content: Sidebar + Map -->
|
||||||
|
<div class="hvac-map-layout">
|
||||||
|
<!-- Left Sidebar -->
|
||||||
|
<aside class="hvac-sidebar" role="region" aria-label="Trainer directory">
|
||||||
|
<div class="hvac-sidebar-header">
|
||||||
|
<span class="hvac-results-summary" aria-live="polite">
|
||||||
|
<span id="hvac-trainer-count">0</span> trainers
|
||||||
|
</span>
|
||||||
|
<!-- Mobile collapse toggle -->
|
||||||
|
<button type="button"
|
||||||
|
class="hvac-sidebar-toggle"
|
||||||
|
aria-expanded="true"
|
||||||
|
aria-controls="hvac-sidebar-content"
|
||||||
|
aria-label="Toggle trainer list">
|
||||||
|
<span class="dashicons dashicons-arrow-down-alt2" aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="hvac-sidebar-content" class="hvac-sidebar-content">
|
||||||
|
<!-- Trainer List (scrollable) -->
|
||||||
|
<div id="hvac-trainer-grid" class="hvac-trainer-list">
|
||||||
|
<div class="hvac-grid-loading">
|
||||||
|
<span class="dashicons dashicons-update-alt hvac-spin" aria-hidden="true"></span>
|
||||||
|
Loading trainers...
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Near Me Button -->
|
<!-- Load More Button -->
|
||||||
<button type="button" id="hvac-near-me-btn" class="hvac-near-me-btn">
|
<div class="hvac-load-more-wrapper" style="display: none;">
|
||||||
<span class="dashicons dashicons-location-alt"></span>
|
<button type="button" id="hvac-load-more" class="hvac-btn-secondary">
|
||||||
Near Me
|
Load More
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Filters Header -->
|
|
||||||
<div class="hvac-filters-header">
|
|
||||||
<span class="hvac-filters-label">Filters:</span>
|
|
||||||
<button type="button" class="hvac-clear-filters" style="display: none;">
|
|
||||||
Clear All
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- State Filter -->
|
<!-- CTA -->
|
||||||
<div class="hvac-filter-group">
|
<div class="hvac-sidebar-cta">
|
||||||
<label for="hvac-filter-state">State / Province</label>
|
<p>Want to be listed in our directory?</p>
|
||||||
<select id="hvac-filter-state" class="hvac-filter-select">
|
<a href="<?php echo esc_url(site_url('/trainer/registration/')); ?>" class="hvac-btn-primary hvac-btn-small">
|
||||||
<option value="">All States</option>
|
Become A Trainer
|
||||||
<?php foreach ($filter_options['states'] as $state): ?>
|
</a>
|
||||||
<option value="<?php echo esc_attr($state); ?>"><?php echo esc_html($state); ?></option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Certification Filter -->
|
|
||||||
<div class="hvac-filter-group">
|
|
||||||
<label for="hvac-filter-certification">Certification</label>
|
|
||||||
<select id="hvac-filter-certification" class="hvac-filter-select">
|
|
||||||
<option value="">All Certifications</option>
|
|
||||||
<?php foreach ($filter_options['certifications'] as $cert): ?>
|
|
||||||
<option value="<?php echo esc_attr($cert); ?>"><?php echo esc_html($cert); ?></option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Training Format Filter -->
|
|
||||||
<div class="hvac-filter-group">
|
|
||||||
<label for="hvac-filter-format">Training Format</label>
|
|
||||||
<select id="hvac-filter-format" class="hvac-filter-select">
|
|
||||||
<option value="">All Formats</option>
|
|
||||||
<?php foreach ($filter_options['training_formats'] as $format): ?>
|
|
||||||
<option value="<?php echo esc_attr($format); ?>"><?php echo esc_html($format); ?></option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Marker Type Toggles -->
|
|
||||||
<div class="hvac-marker-toggles">
|
|
||||||
<label class="hvac-toggle">
|
|
||||||
<input type="checkbox" id="hvac-show-trainers" checked>
|
|
||||||
<span class="hvac-toggle-slider"></span>
|
|
||||||
<span class="hvac-toggle-label">Show Trainers</span>
|
|
||||||
</label>
|
|
||||||
<label class="hvac-toggle">
|
|
||||||
<input type="checkbox" id="hvac-show-venues" checked>
|
|
||||||
<span class="hvac-toggle-slider"></span>
|
|
||||||
<span class="hvac-toggle-label">Show Venues</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Active Filters -->
|
|
||||||
<div class="hvac-active-filters"></div>
|
|
||||||
|
|
||||||
<!-- Results Count -->
|
|
||||||
<div class="hvac-results-count">
|
|
||||||
<span id="hvac-trainer-count">0</span> trainers, <span id="hvac-venue-count">0</span> venues
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</aside>
|
||||||
|
|
||||||
<!-- Trainer Directory Grid -->
|
<!-- Map Container -->
|
||||||
<div class="hvac-trainer-directory-section">
|
<div class="hvac-map-container" role="region" aria-label="Training locations map">
|
||||||
<h2>Trainers Directory</h2>
|
<div id="hvac-training-map" class="hvac-google-map">
|
||||||
<div id="hvac-trainer-grid" class="hvac-trainer-grid">
|
<?php if (!$api_key_configured): ?>
|
||||||
<div class="hvac-grid-loading">
|
<div class="hvac-map-error">
|
||||||
<span class="dashicons dashicons-update-alt hvac-spin"></span>
|
<span class="dashicons dashicons-warning" aria-hidden="true"></span>
|
||||||
Loading trainers...
|
<p><strong>Map Configuration Required</strong></p>
|
||||||
|
<p>The Google Maps API key is not configured. <?php if (current_user_can('manage_options')): ?>Please configure it in <a href="<?php echo admin_url('admin.php?page=hvac-trainer-profile-settings'); ?>">Trainer Profile Settings</a>.<?php else: ?>Please contact the site administrator.<?php endif; ?></p>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="hvac-map-loading">
|
||||||
|
<span class="dashicons dashicons-location" aria-hidden="true"></span>
|
||||||
|
<p>Loading map...</p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Map Legend Overlay -->
|
||||||
|
<div class="hvac-map-legend">
|
||||||
|
<div class="hvac-legend-item">
|
||||||
|
<span class="hvac-legend-marker hvac-legend-trainer" aria-hidden="true"></span>
|
||||||
|
<span>Trainer</span>
|
||||||
|
</div>
|
||||||
|
<div class="hvac-legend-item">
|
||||||
|
<span class="hvac-legend-marker hvac-legend-venue" aria-hidden="true"></span>
|
||||||
|
<span>Venue</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Load More Button -->
|
<!-- Map Toggles Overlay -->
|
||||||
<div class="hvac-load-more-wrapper" style="display: none;">
|
<div class="hvac-map-toggles">
|
||||||
<button type="button" id="hvac-load-more" class="hvac-btn-secondary">
|
<label class="hvac-toggle-compact">
|
||||||
Load More
|
<input type="checkbox" id="hvac-show-trainers" checked>
|
||||||
</button>
|
<span class="hvac-toggle-label">Trainers</span>
|
||||||
|
</label>
|
||||||
|
<label class="hvac-toggle-compact">
|
||||||
|
<input type="checkbox" id="hvac-show-venues" checked>
|
||||||
|
<span class="hvac-toggle-label">Venues</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- CTA Section -->
|
|
||||||
<div class="hvac-cta-section">
|
|
||||||
<p>Are you an HVAC Trainer that wants to be listed in our directory?</p>
|
|
||||||
<a href="<?php echo esc_url(site_url('/trainer/registration/')); ?>" class="hvac-btn-primary">Become A Trainer</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -168,7 +206,7 @@ $filter_options = $find_training->get_filter_options();
|
||||||
<div class="hvac-modal-overlay"></div>
|
<div class="hvac-modal-overlay"></div>
|
||||||
<div class="hvac-modal-content">
|
<div class="hvac-modal-content">
|
||||||
<div class="hvac-modal-loading">
|
<div class="hvac-modal-loading">
|
||||||
<span class="dashicons dashicons-update-alt hvac-spin"></span>
|
<span class="dashicons dashicons-update-alt hvac-spin" aria-hidden="true"></span>
|
||||||
Loading...
|
Loading...
|
||||||
</div>
|
</div>
|
||||||
<div class="hvac-modal-body"></div>
|
<div class="hvac-modal-body"></div>
|
||||||
|
|
@ -178,21 +216,88 @@ $filter_options = $find_training->get_filter_options();
|
||||||
<!-- Venue Info Modal -->
|
<!-- Venue Info Modal -->
|
||||||
<div id="hvac-venue-modal" class="hvac-training-modal" style="display: none;" role="dialog" aria-modal="true" aria-labelledby="venue-modal-title">
|
<div id="hvac-venue-modal" class="hvac-training-modal" style="display: none;" role="dialog" aria-modal="true" aria-labelledby="venue-modal-title">
|
||||||
<div class="hvac-modal-overlay"></div>
|
<div class="hvac-modal-overlay"></div>
|
||||||
<div class="hvac-modal-content">
|
<div class="hvac-modal-content hvac-venue-modal-content">
|
||||||
<div class="hvac-venue-modal-header">
|
<div class="hvac-venue-modal-header">
|
||||||
<h2 id="venue-modal-title"></h2>
|
<h2 id="venue-modal-title"></h2>
|
||||||
<button class="hvac-modal-close" aria-label="Close modal">×</button>
|
<button class="hvac-modal-close" aria-label="Close modal">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="hvac-venue-modal-body">
|
<div class="hvac-venue-modal-body">
|
||||||
<p class="hvac-venue-address"></p>
|
<p class="hvac-venue-address"></p>
|
||||||
<div class="hvac-venue-events">
|
<p class="hvac-venue-phone"></p>
|
||||||
<h4>Upcoming Events at this Venue</h4>
|
<p class="hvac-venue-capacity"></p>
|
||||||
<ul class="hvac-venue-events-list"></ul>
|
|
||||||
|
<div class="hvac-venue-description"></div>
|
||||||
|
|
||||||
|
<!-- Equipment Badges -->
|
||||||
|
<div class="hvac-venue-equipment" style="display: none;">
|
||||||
|
<h4>Equipment</h4>
|
||||||
|
<div class="hvac-badge-list hvac-equipment-badges"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Amenities Badges -->
|
||||||
|
<div class="hvac-venue-amenities" style="display: none;">
|
||||||
|
<h4>Amenities</h4>
|
||||||
|
<div class="hvac-badge-list hvac-amenities-badges"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Upcoming Events -->
|
||||||
|
<div class="hvac-venue-events">
|
||||||
|
<h4>Upcoming Events</h4>
|
||||||
|
<ul class="hvac-venue-events-list"></ul>
|
||||||
|
<p class="hvac-venue-no-events" style="display: none;">No upcoming events scheduled.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="hvac-venue-actions">
|
||||||
|
<a href="#" class="hvac-venue-directions hvac-btn-secondary" target="_blank" rel="noopener">
|
||||||
|
<span class="dashicons dashicons-location" aria-hidden="true"></span>
|
||||||
|
Get Directions
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Contact Form -->
|
||||||
|
<div class="hvac-venue-contact-section">
|
||||||
|
<h4>Contact This Training Lab</h4>
|
||||||
|
<form class="hvac-venue-contact-form" data-venue-id="">
|
||||||
|
<div class="hvac-form-row">
|
||||||
|
<div class="hvac-form-group">
|
||||||
|
<label for="venue-contact-first-name">First Name <span class="required">*</span></label>
|
||||||
|
<input type="text" id="venue-contact-first-name" name="first_name" required>
|
||||||
|
</div>
|
||||||
|
<div class="hvac-form-group">
|
||||||
|
<label for="venue-contact-last-name">Last Name <span class="required">*</span></label>
|
||||||
|
<input type="text" id="venue-contact-last-name" name="last_name" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hvac-form-row">
|
||||||
|
<div class="hvac-form-group">
|
||||||
|
<label for="venue-contact-email">Email <span class="required">*</span></label>
|
||||||
|
<input type="email" id="venue-contact-email" name="email" required>
|
||||||
|
</div>
|
||||||
|
<div class="hvac-form-group">
|
||||||
|
<label for="venue-contact-phone">Phone</label>
|
||||||
|
<input type="tel" id="venue-contact-phone" name="phone">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hvac-form-group">
|
||||||
|
<label for="venue-contact-company">Company</label>
|
||||||
|
<input type="text" id="venue-contact-company" name="company">
|
||||||
|
</div>
|
||||||
|
<div class="hvac-form-group">
|
||||||
|
<label for="venue-contact-message">Message</label>
|
||||||
|
<textarea id="venue-contact-message" name="message" rows="4" placeholder="Tell us about your training needs..."></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="hvac-btn-primary">Send Message</button>
|
||||||
|
</form>
|
||||||
|
<div class="hvac-form-success" style="display: none;">
|
||||||
|
<span class="dashicons dashicons-yes-alt" aria-hidden="true"></span>
|
||||||
|
<p>Your message has been sent! The training lab will be in touch soon.</p>
|
||||||
|
</div>
|
||||||
|
<div class="hvac-form-error" style="display: none;">
|
||||||
|
<span class="dashicons dashicons-warning" aria-hidden="true"></span>
|
||||||
|
<p>There was a problem sending your message. Please try again.</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="#" class="hvac-venue-directions hvac-btn-secondary" target="_blank">
|
|
||||||
<span class="dashicons dashicons-location"></span>
|
|
||||||
Get Directions
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue