upskill-event-manager/assets/js/find-trainer.js
Ben c3e7fe9140 feat: comprehensive HVAC plugin development framework and modernization
## Major Enhancements

### 🏗️ Architecture & Infrastructure
- Implement comprehensive Docker testing infrastructure with hermetic environment
- Add Forgejo Actions CI/CD pipeline for automated deployments
- Create Page Object Model (POM) testing architecture reducing test duplication by 90%
- Establish security-first development patterns with input validation and output escaping

### 🧪 Testing Framework Modernization
- Migrate 146+ tests from 80 duplicate files to centralized architecture
- Add comprehensive E2E test suites for all user roles and workflows
- Implement WordPress error detection with automatic site health monitoring
- Create robust browser lifecycle management with proper cleanup

### 📚 Documentation & Guides
- Add comprehensive development best practices guide
- Create detailed administrator setup documentation
- Establish user guides for trainers and master trainers
- Document security incident reports and migration guides

### 🔧 Core Plugin Features
- Enhance trainer profile management with certification system
- Improve find trainer functionality with advanced filtering
- Strengthen master trainer area with content management
- Add comprehensive venue and organizer management

### 🛡️ Security & Reliability
- Implement security-first patterns throughout codebase
- Add comprehensive input validation and output escaping
- Create secure credential management system
- Establish proper WordPress role-based access control

### 🎯 WordPress Integration
- Strengthen singleton pattern implementation across all classes
- Enhance template hierarchy with proper WordPress integration
- Improve page manager with hierarchical URL structure
- Add comprehensive shortcode and menu system

### 🔍 Developer Experience
- Add extensive debugging and troubleshooting tools
- Create comprehensive test data seeding scripts
- Implement proper error handling and logging
- Establish consistent code patterns and standards

### 📊 Performance & Optimization
- Optimize database queries and caching strategies
- Improve asset loading and script management
- Enhance template rendering performance
- Streamline user experience across all interfaces

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-29 11:26:10 -03:00

964 lines
No EOL
39 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Find a Trainer Page JavaScript
* Handles filtering, modals, and AJAX interactions
*
* @package HVAC_Plugin
* @since 1.0.0
*/
(function($) {
'use strict';
// Cache DOM elements
let $filterModal, $trainerModal, $contactForm;
let currentFilter = '';
let activeFilters = {};
let currentPage = 1;
let isLoading = false;
// Initialize on document ready
$(document).ready(function() {
initializeElements();
bindEvents();
// Handle direct profile URL access
handleDirectProfileAccess();
// Enable MapGeo interaction handling (only if not showing direct profile)
if (!hvac_find_trainer.show_direct_profile) {
preventMapGeoSidebarContent();
interceptMapGeoMarkers();
// Additional MapGeo integration after map loads
setTimeout(function() {
initializeMapGeoEvents();
}, 2000); // Give MapGeo time to initialize
}
});
/**
* Initialize cached elements
*/
function initializeElements() {
$filterModal = $('#hvac-filter-modal');
$trainerModal = $('#hvac-trainer-modal');
$contactForm = $('#hvac-contact-form');
// CRITICAL: Ensure modals are hidden on initialization
if ($filterModal.length) {
$filterModal.removeClass('modal-active active show').css({
'display': 'none',
'visibility': 'hidden',
'opacity': '0'
});
}
if ($trainerModal.length) {
$trainerModal.css('display', 'none');
}
}
/**
* Prevent MapGeo from displaying content in its sidebar
* This ensures trainer cards only appear in our Container 5
*/
function preventMapGeoSidebarContent() {
// Remove any MapGeo sidebar content immediately
$('.igm_content_right_1_3').remove();
$('.igm_content_gutter').remove();
// Watch for any dynamic content injection from MapGeo
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.addedNodes.length) {
mutation.addedNodes.forEach(function(node) {
if (node.nodeType === 1) { // Element node
// Remove any MapGeo sidebar that gets added
if ($(node).hasClass('igm_content_right_1_3') ||
$(node).hasClass('igm_content_gutter')) {
$(node).remove();
}
// Also check children
$(node).find('.igm_content_right_1_3, .igm_content_gutter').remove();
}
});
}
});
});
// Observe the map section for changes
const mapSection = document.querySelector('.hvac-map-section');
if (mapSection) {
observer.observe(mapSection, {
childList: true,
subtree: true
});
}
// Also observe the entire page for MapGeo injections
observer.observe(document.body, {
childList: true,
subtree: true
});
}
/**
* Initialize MapGeo-specific event handlers after map loads
*/
function initializeMapGeoEvents() {
console.log('Initializing MapGeo events...');
// Create the main MapGeo handler function
// This replaces the early version created in the page template
window.hvacMainShowTrainerModal = function(data) {
console.log('MapGeo custom action triggered with data:', data);
// Method 1: Use profile_id if available (most reliable)
let profileId = null;
if (data && data.profile_id && data.profile_id.trim() !== '') {
profileId = data.profile_id.trim();
} else if (data && data.id && data.id.toString().startsWith('trainer_')) {
// Extract profile ID from marker ID format "trainer_123"
profileId = data.id.replace('trainer_', '');
} else if (data && data.name && data.name.match(/^\d+$/)) {
// Check if name field actually contains the profile ID (common case)
profileId = data.name;
} else if (data && data.id && data.id.match(/^\d+$/)) {
// Check if id field contains just the profile ID number
profileId = data.id;
}
console.log('Extracted profile ID:', profileId);
if (profileId) {
// Find trainer card by profile ID (most reliable method)
const $matchingCard = $('.hvac-trainer-card[data-profile-id="' + profileId + '"]');
if ($matchingCard.length > 0 && !$matchingCard.hasClass('hvac-champion-card')) {
console.log('Found matching trainer card by profile ID:', profileId);
// Extract trainer data from the card
const trainerData = {
profile_id: profileId,
name: $matchingCard.find('.hvac-trainer-name a, .hvac-trainer-name .hvac-champion-name').text().trim(),
city: $matchingCard.find('.hvac-trainer-location').text().split(',')[0],
state: $matchingCard.find('.hvac-trainer-location').text().split(',')[1]?.trim(),
certification_type: $matchingCard.find('.hvac-trainer-certification').text(), // Legacy compatibility
certifications: [],
profile_image: $matchingCard.find('.hvac-trainer-image img:not(.hvac-mq-badge)').attr('src') || '',
business_type: 'Independent Contractor', // Mock data
event_count: parseInt($matchingCard.data('event-count')) || 0,
training_formats: 'In-Person, Virtual',
training_locations: 'On-site, Remote',
upcoming_events: []
};
// Extract certifications from card badges
$matchingCard.find('.hvac-trainer-cert-badge').each(function() {
const certText = $(this).text().trim();
if (certText && certText !== 'HVAC Trainer') {
trainerData.certifications.push({
type: certText,
status: $(this).hasClass('hvac-cert-legacy') ? 'legacy' : 'active'
});
}
});
// Show the trainer modal
showTrainerModal(trainerData);
return; // Successfully handled
} else if ($matchingCard.length > 0 && $matchingCard.hasClass('hvac-champion-card')) {
console.log('Clicked marker is for a Champion, not showing modal');
return; // Champions don't get modals
} else {
console.warn('No trainer card found for profile ID:', profileId);
}
}
// Fallback Method 2: Try to extract trainer name and match
let trainerName = null;
// Try various name fields
if (data && data.name && data.name.trim() !== '+' && !data.name.match(/^\d+$/)) {
trainerName = data.name.trim();
} else if (data && data.title && data.title.trim() !== '+') {
trainerName = data.title.trim();
}
// Try content field
if (!trainerName && data && data.content) {
console.log('Trying to extract trainer from content:', data.content);
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.content;
const nameElements = tempDiv.querySelectorAll('h4, strong, .trainer-name, [class*="name"]');
if (nameElements.length > 0) {
trainerName = nameElements[0].textContent.trim();
}
}
// Try tooltipContent
if (!trainerName && data && data.tooltipContent) {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.tooltipContent;
const strongElements = tempDiv.querySelectorAll('strong');
if (strongElements.length > 0) {
trainerName = strongElements[0].textContent.trim();
}
}
console.log('Extracted trainer name (fallback):', trainerName);
if (trainerName && trainerName !== '+') {
// Try to find matching trainer by name
let $matchingCard = $('.hvac-trainer-card').filter(function() {
const cardName = $(this).find('.hvac-trainer-name a, .hvac-trainer-name .hvac-champion-name').text().trim();
return cardName === trainerName;
});
// If exact match not found, try partial matching
if ($matchingCard.length === 0) {
$matchingCard = $('.hvac-trainer-card').filter(function() {
const cardName = $(this).find('.hvac-trainer-name a, .hvac-trainer-name .hvac-champion-name').text().trim();
return cardName.toLowerCase().includes(trainerName.toLowerCase()) ||
trainerName.toLowerCase().includes(cardName.toLowerCase());
});
}
if ($matchingCard.length > 0 && !$matchingCard.hasClass('hvac-champion-card')) {
console.log('Found matching trainer card by name:', trainerName);
// Extract trainer data from the card
const trainerData = {
profile_id: $matchingCard.data('profile-id'),
name: $matchingCard.find('.hvac-trainer-name a, .hvac-trainer-name .hvac-champion-name').text().trim(),
city: $matchingCard.find('.hvac-trainer-location').text().split(',')[0],
state: $matchingCard.find('.hvac-trainer-location').text().split(',')[1]?.trim(),
certification_type: $matchingCard.find('.hvac-trainer-certification').text(), // Legacy compatibility
certifications: [],
profile_image: $matchingCard.find('.hvac-trainer-image img:not(.hvac-mq-badge)').attr('src') || '',
business_type: 'Independent Contractor', // Mock data
event_count: parseInt($matchingCard.data('event-count')) || 0,
training_formats: 'In-Person, Virtual',
training_locations: 'On-site, Remote',
upcoming_events: []
};
// Extract certifications from card badges
$matchingCard.find('.hvac-trainer-cert-badge').each(function() {
const certText = $(this).text().trim();
if (certText && certText !== 'HVAC Trainer') {
trainerData.certifications.push({
type: certText,
status: $(this).hasClass('hvac-cert-legacy') ? 'legacy' : 'active'
});
}
});
// Show the trainer modal
showTrainerModal(trainerData);
} else if ($matchingCard.length > 0 && $matchingCard.hasClass('hvac-champion-card')) {
console.log('Matched trainer is a Champion, not showing modal');
} else {
console.warn('No matching trainer found for name:', trainerName);
console.log('Available trainers:',
$('.hvac-trainer-card .hvac-trainer-name a, .hvac-trainer-card .hvac-trainer-name .hvac-champion-name').map(function() {
return $(this).text().trim();
}).get()
);
}
} else {
console.warn('Could not extract valid trainer identifier from MapGeo data:', data);
console.log('Available data properties:', Object.keys(data || {}));
console.log('Available profile IDs on page:',
$('.hvac-trainer-card').map(function() {
return $(this).data('profile-id');
}).get()
);
}
};
// Replace the early function with the main one
window.hvacShowTrainerModal = window.hvacMainShowTrainerModal;
// Process any queued calls from before the main script loaded
if (window.hvacPendingModalCalls && window.hvacPendingModalCalls.length > 0) {
console.log('Processing', window.hvacPendingModalCalls.length, 'queued MapGeo calls');
window.hvacPendingModalCalls.forEach(function(data) {
window.hvacMainShowTrainerModal(data);
});
window.hvacPendingModalCalls = []; // Clear the queue
}
console.log('MapGeo custom action function created: window.hvacShowTrainerModal');
}
/**
* Prevent MapGeo from showing content in sidebar (if needed)
*/
function interceptMapGeoMarkers() {
// This function now primarily handles preventing MapGeo sidebar content
// The actual marker clicks are handled via the MapGeo custom action: window.hvacShowTrainerModal
// Handle any legacy view profile links if they exist in tooltips/popups
$(document).on('click', '.hvac-view-profile, .hvac-marker-popup button', function(e) {
e.preventDefault();
e.stopPropagation();
const profileId = $(this).data('profile-id');
if (profileId) {
// Find the corresponding trainer data from the cards
const $trainerCard = $('.hvac-trainer-card[data-profile-id="' + profileId + '"]');
if ($trainerCard.length > 0 && !$trainerCard.hasClass('hvac-champion-card')) {
// Get trainer name and trigger the MapGeo custom action
const trainerName = $trainerCard.find('.hvac-trainer-name a, .hvac-trainer-name .hvac-champion-name').text().trim();
// Trigger the same function MapGeo would call
if (window.hvacShowTrainerModal) {
window.hvacShowTrainerModal({ name: trainerName });
}
}
}
});
}
/**
* Bind all event handlers
*/
function bindEvents() {
// Filter button clicks - handle both class variations
$('.hvac-filter-btn, .hvac-filter-button').on('click', handleFilterClick);
// Filter modal apply
$('.hvac-filter-apply').on('click', applyFilters);
// Clear all filters button
$('.hvac-clear-filters').on('click', clearAllFilters);
// Trainer profile clicks - using event delegation
$(document).on('click', '.hvac-open-profile', handleProfileClick);
// Modal close buttons and backdrop clicks
$('.hvac-modal-close').on('click', closeModals);
// Click on modal backdrop to close
$filterModal.on('click', function(e) {
if ($(e.target).is('#hvac-filter-modal')) {
closeModals();
}
});
$trainerModal.on('click', function(e) {
if ($(e.target).is('#hvac-trainer-modal')) {
closeModals();
}
});
// Escape key to close modals
$(document).on('keydown', function(e) {
if (e.key === 'Escape') {
closeModals();
}
});
// Search input
$('.hvac-search-input').on('input', debounce(handleSearch, 500));
// Contact form submission (both modal and direct forms)
$contactForm.on('submit', handleContactSubmit);
$(document).on('submit', '#hvac-direct-contact-form', handleContactSubmit);
// Pagination clicks
$(document).on('click', '.hvac-pagination a, .hvac-page-link', handlePagination);
// Active filter removal
$(document).on('click', '.hvac-active-filter button', removeActiveFilter);
}
/**
* Handle filter button click
*/
function handleFilterClick(e) {
e.preventDefault();
e.stopPropagation();
currentFilter = $(this).data('filter');
// Load real filter options via AJAX
loadFilterOptions(currentFilter);
}
/**
* Load filter options via AJAX
*/
function loadFilterOptions(filterType) {
if (isLoading) return;
isLoading = true;
// Show loading state for filter button
$(`.hvac-filter-btn[data-filter="${filterType}"]`).addClass('loading');
$.post(hvac_find_trainer.ajax_url, {
action: 'hvac_get_filter_options',
filter_type: filterType,
nonce: hvac_find_trainer.nonce
})
.done(function(response) {
if (response.success && response.data.options) {
// Convert the different response formats to standard format
let options = [];
if (filterType === 'business_type') {
// Business types have {value, label, count} format
options = response.data.options;
} else {
// States and other simple arrays need to be converted to {value, label} format
options = response.data.options.map(function(option) {
if (typeof option === 'string') {
return {value: option, label: option};
}
return option;
});
}
showFilterModal({options: options});
} else {
console.error('Failed to load filter options:', response);
// Fallback to empty options
showFilterModal({options: []});
}
})
.fail(function(xhr, status, error) {
console.error('AJAX error loading filter options:', status, error);
// Fallback to empty options
showFilterModal({options: []});
})
.always(function() {
isLoading = false;
$(`.hvac-filter-btn[data-filter="${filterType}"]`).removeClass('loading');
});
}
/**
* Get mock filter options (kept as fallback)
*/
function getMockFilterOptions(filterType) {
const options = {
state: [
{value: 'Alabama', label: 'Alabama'},
{value: 'Alaska', label: 'Alaska'},
{value: 'Arizona', label: 'Arizona'},
{value: 'Arkansas', label: 'Arkansas'},
{value: 'California', label: 'California'},
{value: 'Colorado', label: 'Colorado'},
{value: 'Florida', label: 'Florida'},
{value: 'Georgia', label: 'Georgia'},
{value: 'Illinois', label: 'Illinois'},
{value: 'Michigan', label: 'Michigan'},
{value: 'Minnesota', label: 'Minnesota'},
{value: 'Ohio', label: 'Ohio'},
{value: 'Texas', label: 'Texas'},
{value: 'Wisconsin', label: 'Wisconsin'}
],
business_type: [
{value: 'Independent Contractor', label: 'Independent Contractor'},
{value: 'Small Business', label: 'Small Business'},
{value: 'Corporation', label: 'Corporation'},
{value: 'Non-Profit', label: 'Non-Profit'}
],
training_format: [
{value: 'In-Person', label: 'In-Person'},
{value: 'Virtual', label: 'Virtual'},
{value: 'Hybrid', label: 'Hybrid'},
{value: 'Self-Paced', label: 'Self-Paced'}
],
training_resources: [
{value: 'Video Tutorials', label: 'Video Tutorials'},
{value: 'Written Guides', label: 'Written Guides'},
{value: 'Hands-On Training', label: 'Hands-On Training'},
{value: 'Certification Programs', label: 'Certification Programs'}
]
};
return {
options: options[filterType] || []
};
}
/**
* Show filter modal with options
*/
function showFilterModal(data) {
const $modalTitle = $filterModal.find('.hvac-filter-modal-title');
const $modalOptions = $filterModal.find('.hvac-filter-options');
// Set title
let title = currentFilter.replace(/_/g, ' ');
title = title.charAt(0).toUpperCase() + title.slice(1);
$modalTitle.text(title);
// Build options HTML
let optionsHtml = '';
const currentValues = activeFilters[currentFilter] || [];
data.options.forEach(function(option) {
const checked = currentValues.includes(option.value) ? 'checked' : '';
optionsHtml += `
<div class="hvac-filter-option">
<input type="checkbox" id="filter_${option.value.replace(/\s+/g, '_')}" value="${option.value}" ${checked}>
<label for="filter_${option.value.replace(/\s+/g, '_')}">${option.label}</label>
</div>
`;
});
$modalOptions.html(optionsHtml);
// Show modal with proper CSS class and inline style overrides
$filterModal.addClass('modal-active');
// Force styles with higher specificity by setting them directly on the element
$filterModal[0].style.setProperty('display', 'flex', 'important');
$filterModal[0].style.setProperty('visibility', 'visible', 'important');
$filterModal[0].style.setProperty('opacity', '1', 'important');
}
/**
* Apply selected filters
*/
function applyFilters() {
const selectedValues = [];
$filterModal.find('.hvac-filter-option input:checked').each(function() {
selectedValues.push($(this).val());
});
if (selectedValues.length > 0) {
activeFilters[currentFilter] = selectedValues;
} else {
delete activeFilters[currentFilter];
}
updateActiveFiltersDisplay();
updateClearButtonVisibility();
currentPage = 1;
loadFilteredTrainers();
closeModals();
}
/**
* Update active filters display
*/
function updateActiveFiltersDisplay() {
const $container = $('.hvac-active-filters');
let html = '';
for (const [filter, values] of Object.entries(activeFilters)) {
values.forEach(function(value) {
html += `
<div class="hvac-active-filter" data-filter="${filter}" data-value="${value}">
${value}
<button type="button" aria-label="Remove filter">×</button>
</div>
`;
});
}
$container.html(html);
}
/**
* Remove active filter
*/
function removeActiveFilter(e) {
e.preventDefault();
const $filter = $(this).parent();
const filter = $filter.data('filter');
const value = $filter.data('value');
if (activeFilters[filter]) {
activeFilters[filter] = activeFilters[filter].filter(v => v !== value);
if (activeFilters[filter].length === 0) {
delete activeFilters[filter];
}
}
updateActiveFiltersDisplay();
updateClearButtonVisibility();
currentPage = 1;
loadFilteredTrainers();
}
/**
* Handle trainer profile click
*/
function handleProfileClick(e) {
e.preventDefault();
e.stopPropagation();
const $card = $(this).closest('.hvac-trainer-card');
// Don't allow clicks on Champion cards
if ($card.hasClass('hvac-champion-card')) {
return false;
}
const profileId = $(this).data('profile-id');
// Get trainer data from the card
const trainerData = {
profile_id: profileId,
name: $card.find('.hvac-trainer-name a').text(),
city: $card.find('.hvac-trainer-location').text().split(',')[0],
state: $card.find('.hvac-trainer-location').text().split(',')[1]?.trim(),
certification_type: $card.find('.hvac-trainer-certification').text(), // Legacy field for compatibility
certifications: [], // Will be populated from card badges
profile_image: $card.find('.hvac-trainer-image img:not(.hvac-mq-badge)').attr('src') || '',
business_type: 'Independent Contractor', // Mock data
event_count: parseInt($card.data('event-count')) || 0, // Real event count from data attribute
training_formats: 'In-Person, Virtual',
training_locations: 'On-site, Remote',
upcoming_events: [] // Mock empty events
};
// Extract certifications from card badges
$card.find('.hvac-trainer-cert-badge').each(function() {
const certText = $(this).text().trim();
if (certText && certText !== 'HVAC Trainer') {
trainerData.certifications.push({
type: certText,
status: $(this).hasClass('hvac-cert-legacy') ? 'legacy' : 'active'
});
}
});
showTrainerModal(trainerData);
}
/**
* Show trainer profile modal
* Made global so MapGeo can access it
*/
function showTrainerModal(trainer) {
// Update modal title
$trainerModal.find('.hvac-modal-title').text(trainer.name);
// Update profile image
const $imgContainer = $trainerModal.find('.hvac-modal-image');
let imageHtml = '';
if (trainer.profile_image) {
imageHtml = `<img src="${trainer.profile_image}" alt="${trainer.name}">`;
} else {
imageHtml = '<div class="hvac-trainer-avatar"><span class="dashicons dashicons-businessperson"></span></div>';
}
// Add mQ badge overlay for certified trainers
let hasTrainerCert = false;
if (trainer.certifications && trainer.certifications.length > 0) {
// Check if any certification is a trainer certification
hasTrainerCert = trainer.certifications.some(cert =>
cert.type.toLowerCase().includes('trainer') ||
cert.type === 'measureQuick Certified Trainer'
);
} else if (trainer.certification_type === 'Certified measureQuick Trainer' ||
trainer.certification_type === 'measureQuick Certified Trainer') {
// Fallback for legacy single certification
hasTrainerCert = true;
}
if (hasTrainerCert) {
imageHtml += '<div class="hvac-mq-badge-overlay"><img src="/wp-content/uploads/2025/08/mQ-Certified-trainer.png" alt="measureQuick Certified Trainer" class="hvac-mq-badge"></div>';
}
$imgContainer.html(imageHtml);
// Update profile info
$trainerModal.find('.hvac-modal-location').text(`${trainer.city}, ${trainer.state}`);
// Update certifications section - handle both single and multiple certifications
const $certContainer = $trainerModal.find('.hvac-modal-certification-badges');
let certHtml = '';
if (trainer.certifications && trainer.certifications.length > 0) {
// Show multiple certifications as badges
trainer.certifications.forEach(function(cert) {
const badgeClass = cert.type.toLowerCase()
.replace('measurequick certified ', '')
.replace(/\s+/g, '-');
const legacyClass = cert.status === 'legacy' ? ' hvac-cert-legacy' : '';
certHtml += `<span class="hvac-trainer-cert-badge hvac-cert-${badgeClass}${legacyClass}">${cert.type}</span>`;
});
} else if (trainer.certification_type && trainer.certification_type !== 'HVAC Trainer') {
// Fallback to legacy single certification
const badgeClass = trainer.certification_type.toLowerCase()
.replace('measurequick certified ', '')
.replace(/\s+/g, '-');
certHtml = `<span class="hvac-trainer-cert-badge hvac-cert-${badgeClass}">${trainer.certification_type}</span>`;
} else {
// Default fallback
certHtml = '<span class="hvac-trainer-cert-badge hvac-cert-default">HVAC Trainer</span>';
}
$certContainer.html(certHtml);
$trainerModal.find('.hvac-modal-business').text(trainer.business_type || '');
$trainerModal.find('.hvac-modal-events span').text(trainer.event_count || 0);
// Update training details
$trainerModal.find('.hvac-training-formats').text(trainer.training_formats || 'Various');
$trainerModal.find('.hvac-training-locations').text(trainer.training_locations || 'On-site');
// Show loading state for events
$trainerModal.find('.hvac-events-list').html('<li>Loading upcoming events...</li>');
// Set hidden fields for contact form
$contactForm.find('input[name="trainer_id"]').val(trainer.user_id || '');
$contactForm.find('input[name="trainer_profile_id"]').val(trainer.profile_id);
// Reset contact form
$contactForm[0].reset();
$('.hvac-form-message').hide();
// Show modal
$trainerModal.fadeIn(300);
// Fetch upcoming events via AJAX
fetchUpcomingEvents(trainer.profile_id);
}
/**
* Fetch upcoming events for a trainer via AJAX
*/
function fetchUpcomingEvents(profileId) {
if (!profileId) {
$trainerModal.find('.hvac-events-list').html('<li>No upcoming events scheduled</li>');
return;
}
$.post(hvac_find_trainer.ajax_url, {
action: 'hvac_get_trainer_upcoming_events',
nonce: hvac_find_trainer.nonce,
profile_id: profileId
}, function(response) {
if (response.success && response.data.events) {
let eventsHtml = '';
if (response.data.events.length > 0) {
response.data.events.forEach(function(event) {
eventsHtml += `<li><a href="${event.url}" target="_blank">${event.title}</a> - ${event.date}</li>`;
});
} else {
eventsHtml = '<li>No upcoming events scheduled</li>';
}
$trainerModal.find('.hvac-events-list').html(eventsHtml);
} else {
$trainerModal.find('.hvac-events-list').html('<li>No upcoming events scheduled</li>');
}
}).fail(function() {
$trainerModal.find('.hvac-events-list').html('<li>Unable to load events</li>');
});
}
/**
* Handle contact form submission
*/
function handleContactSubmit(e) {
e.preventDefault();
const $form = $(this);
const $submitBtn = $form.find('.hvac-form-submit');
const $successMsg = $form.find('.hvac-form-success');
const $errorMsg = $form.find('.hvac-form-error');
const originalText = $submitBtn.text();
$submitBtn.text('Sending...').prop('disabled', true);
// For now, just show success message
setTimeout(function() {
$successMsg.show();
$errorMsg.hide();
$form[0].reset();
$submitBtn.text(originalText).prop('disabled', false);
// Hide success message after 5 seconds
setTimeout(function() {
$successMsg.fadeOut();
}, 5000);
}, 1000);
}
/**
* Handle search input
*/
function handleSearch() {
const searchTerm = $('.hvac-search-input').val();
if (isLoading) return;
updateClearButtonVisibility();
currentPage = 1;
loadFilteredTrainers();
}
/**
* Handle pagination
*/
function handlePagination(e) {
e.preventDefault();
currentPage = $(this).data('page');
loadFilteredTrainers();
// Scroll to top of trainer grid
$('html, body').animate({
scrollTop: $('.hvac-trainer-directory-container').offset().top - 100
}, 500);
}
/**
* Load filtered trainers via AJAX
*/
function loadFilteredTrainers() {
if (isLoading) return;
isLoading = true;
const $container = $('.hvac-trainer-grid');
let $pagination = $('.hvac-pagination');
// Show loading state
$container.addClass('hvac-loading');
// Prepare data
const data = {
action: 'hvac_filter_trainers',
nonce: hvac_find_trainer.nonce,
page: currentPage,
search: $('.hvac-search-input').val(),
// Flatten the activeFilters for PHP processing
...activeFilters
};
// Make AJAX request
$.post(hvac_find_trainer.ajax_url, data, function(response) {
if (response.success) {
// Our PHP returns an array of trainer card HTML
if (response.data.trainers && response.data.trainers.length > 0) {
const trainersHtml = response.data.trainers.join('');
$container.html(trainersHtml);
} else {
$container.html('<div class="hvac-no-results"><p>No trainers found matching your criteria. Please try adjusting your filters.</p></div>');
}
// Update count display if exists
if (response.data.count !== undefined) {
$('.hvac-trainer-count').text(response.data.count + ' trainers found');
}
// Simple pagination logic - show/hide existing pagination based on results
if (response.data.count > 12) { // Assuming 12 per page
if ($pagination.length > 0) {
$pagination.show();
}
} else {
if ($pagination.length > 0) {
$pagination.hide();
}
}
} else {
console.error('Failed to load trainers:', response);
$container.html('<div class="hvac-no-results"><p>Error loading trainers. Please try again.</p></div>');
}
}).fail(function(xhr) {
console.error('AJAX error:', xhr);
}).always(function() {
isLoading = false;
$container.removeClass('hvac-loading');
});
}
/**
* Close all modals
*/
function closeModals() {
// Remove the modal-active class and force hide styles
$filterModal.removeClass('modal-active');
// Force hide styles with !important
$filterModal[0].style.setProperty('display', 'none', 'important');
$filterModal[0].style.setProperty('visibility', 'hidden', 'important');
$filterModal[0].style.setProperty('opacity', '0', 'important');
$trainerModal.fadeOut(300);
}
/**
* Debounce helper function
*/
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
/**
* Clear all filters
*/
function clearAllFilters() {
activeFilters = {};
$('.hvac-search-input').val('');
updateActiveFiltersDisplay();
updateClearButtonVisibility();
currentPage = 1;
loadFilteredTrainers();
}
/**
* Update clear button visibility
*/
function updateClearButtonVisibility() {
const hasFilters = Object.keys(activeFilters).length > 0;
const hasSearch = $('.hvac-search-input').val().trim() !== '';
if (hasFilters || hasSearch) {
$('.hvac-clear-filters').show();
} else {
$('.hvac-clear-filters').hide();
}
}
/**
* Handle direct profile URL access
* When someone accesses /find-a-trainer/profile/{id}, show the profile and handle interactions
*/
function handleDirectProfileAccess() {
// Check if we're showing a direct profile
if (hvac_find_trainer.show_direct_profile && hvac_find_trainer.direct_profile_id) {
console.log('Direct profile access detected for profile ID:', hvac_find_trainer.direct_profile_id);
// Update page title in browser
if (document.title.includes('Find a Trainer')) {
document.title = document.title.replace('Find a Trainer', 'Trainer Profile');
}
// Bind contact trainer button
$(document).on('click', '.hvac-contact-trainer-btn', function(e) {
e.preventDefault();
const profileId = $(this).data('profile-id');
showTrainerModal(profileId);
});
// Update URL without page reload for clean sharing
const currentUrl = window.location.href;
if (currentUrl.includes('/profile/') && window.history && window.history.replaceState) {
const cleanUrl = currentUrl.split('?')[0]; // Remove any query parameters
window.history.replaceState({}, document.title, cleanUrl);
}
}
}
// Expose showTrainerModal globally for MapGeo integration
window.showTrainerModal = showTrainerModal;
})(jQuery);