diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 9b8a4874..f2440230 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -109,7 +109,9 @@
"Bash(scripts/fix-production-issues.sh:*)",
"Bash(UPSKILL_STAGING_URL=\"https://upskill-staging.measurequick.com\" wp-cli.phar --url=$UPSKILL_STAGING_URL --ssh=root@upskill-staging.measurequick.com user create devAdmin dev.admin@upskillhvac.com --role=hvac_trainer --user_pass=DevAdmin2025!)",
"mcp__zen-mcp__analyze",
- "mcp__zen-mcp__secaudit"
+ "mcp__zen-mcp__secaudit",
+ "WebSearch",
+ "Bash(UPSKILL_STAGING_URL=\"https://upskill-staging.measurequick.com\" wp-cli.phar --url=$UPSKILL_STAGING_URL --ssh=root@upskill-staging.measurequick.com post list --post_type=page --search=dashboard --fields=ID,post_title,post_name,post_status)"
],
"deny": []
},
diff --git a/assets/css/hvac-consolidated-core.css b/assets/css/hvac-consolidated-core.css
index dc7b1066..3b487440 100644
--- a/assets/css/hvac-consolidated-core.css
+++ b/assets/css/hvac-consolidated-core.css
@@ -2871,9 +2871,19 @@ body.hvac-modal-open {
* WordPress-compliant navigation styling
*/
-/* Increase specificity to override theme styles */
-.hvac-page-wrapper .hvac-trainer-menu-wrapper,
-.hvac-trainer-menu-wrapper {
+/* Dashboard navigation - NO container styling */
+.hvac-trainer-dashboard-page .hvac-trainer-menu-wrapper {
+ background: transparent !important;
+ border: none !important;
+ margin-bottom: 20px !important;
+ padding: 0 !important;
+ box-shadow: none !important;
+ width: 100% !important;
+ display: block !important;
+}
+
+/* Other pages - container styling (but NOT dashboard) */
+.hvac-page-wrapper:not(.hvac-trainer-dashboard-page) .hvac-trainer-menu-wrapper {
background: #ffffff !important;
border-bottom: 1px solid #e0e0e0 !important;
margin-bottom: 20px !important;
diff --git a/assets/css/hvac-dashboard.css b/assets/css/hvac-dashboard.css
index 95b7d8d6..04795e85 100644
--- a/assets/css/hvac-dashboard.css
+++ b/assets/css/hvac-dashboard.css
@@ -1083,6 +1083,63 @@
}
}
+/* ==========================================================================
+ Access Denied Styles
+ ========================================================================== */
+
+/* Access denied message styles */
+.hvac-access-denied {
+ max-width: 600px;
+ margin: 60px auto;
+ padding: 40px;
+ text-align: center;
+ background: #fff;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+}
+
+.hvac-access-denied h1 {
+ color: #d63638;
+ margin-bottom: 20px;
+}
+
+.hvac-access-denied p {
+ margin-bottom: 15px;
+ color: #666;
+ line-height: 1.6;
+}
+
+.hvac-access-denied .button {
+ background: #0073aa;
+ color: white;
+ padding: 12px 24px;
+ text-decoration: none;
+ border-radius: 4px;
+ display: inline-block;
+ margin-top: 20px;
+}
+
+.hvac-access-denied .button:hover {
+ background: #005a87;
+ color: white;
+}
+
+/* ==========================================================================
+ Table Controls and Filters (moved from inline)
+ ========================================================================== */
+
+/* Table controls spacing */
+.hvac-table-controls {
+ margin: 20px 0 25px 0;
+ padding: 15px;
+ background: #f8f9fa;
+ border-radius: 6px;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 15px;
+ align-items: center;
+}
+
/* ==========================================================================
Event Edit Form Fixes
========================================================================== */
diff --git a/assets/css/hvac-menu-system.css b/assets/css/hvac-menu-system.css
index a2848d33..c55acf71 100644
--- a/assets/css/hvac-menu-system.css
+++ b/assets/css/hvac-menu-system.css
@@ -3,9 +3,19 @@
* WordPress-compliant navigation styling
*/
-/* Increase specificity to override theme styles */
-.hvac-page-wrapper .hvac-trainer-menu-wrapper,
-.hvac-trainer-menu-wrapper {
+/* Dashboard navigation - NO container styling */
+.hvac-trainer-dashboard-page .hvac-trainer-menu-wrapper {
+ background: transparent !important;
+ border: none !important;
+ margin-bottom: 20px !important;
+ padding: 0 !important;
+ box-shadow: none !important;
+ width: 100% !important;
+ display: block !important;
+}
+
+/* Other pages - container styling (but NOT dashboard) */
+.hvac-page-wrapper:not(.hvac-trainer-dashboard-page) .hvac-trainer-menu-wrapper {
background: #ffffff !important;
border-bottom: 1px solid #e0e0e0 !important;
margin-bottom: 20px !important;
@@ -113,7 +123,7 @@
margin: 0;
padding: 8px 0;
min-width: 200px;
- z-index: 9999;
+ z-index: 99999 !important;
display: none;
}
diff --git a/assets/css/hvac-navigation-fix.css b/assets/css/hvac-navigation-fix.css
index 315cf698..85b1c867 100644
--- a/assets/css/hvac-navigation-fix.css
+++ b/assets/css/hvac-navigation-fix.css
@@ -52,7 +52,7 @@
padding: 8px 0 !important;
margin: 0 !important;
list-style: none !important;
- z-index: 9999 !important;
+ z-index: 99999 !important;
/* Hidden by default */
display: none !important;
@@ -140,12 +140,18 @@
/* Ensure proper z-index layering */
.hvac-trainer-menu-wrapper {
position: relative;
- z-index: 1000;
+ z-index: 10000;
}
.hvac-trainer-menu {
position: relative;
- z-index: 1001;
+ z-index: 10001;
+}
+
+/* Parent menu items need relative positioning */
+.hvac-trainer-menu .menu-item-has-children,
+.hvac-trainer-menu .menu-item.has-children {
+ position: relative;
}
/* Override any conflicting styles */
diff --git a/assets/css/hvac-navigation-simple.css b/assets/css/hvac-navigation-simple.css
index f1991924..319df707 100644
--- a/assets/css/hvac-navigation-simple.css
+++ b/assets/css/hvac-navigation-simple.css
@@ -101,6 +101,15 @@
display: block !important;
max-height: 500px !important;
padding: 8px 0 !important;
+ position: absolute !important;
+ top: 100% !important;
+ left: 0 !important;
+ z-index: 99999 !important;
+ background: white !important;
+ border: 1px solid #e5e7eb !important;
+ border-radius: 8px !important;
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15) !important;
+ min-width: 200px !important;
}
/* Submenu items */
@@ -124,6 +133,19 @@
padding-left: 35px;
}
+/* Hide hamburger menu at desktop widths */
+@media (min-width: 993px) {
+ .hvac-hamburger-menu {
+ display: none !important;
+ }
+
+ .hvac-trainer-menu {
+ display: block !important;
+ visibility: visible !important;
+ opacity: 1 !important;
+ }
+}
+
/* Force visibility on mobile when menu is active */
@media (max-width: 992px) {
.hvac-trainer-menu {
@@ -169,10 +191,21 @@
/* Ensure proper z-index stacking */
.hvac-trainer-nav {
position: relative;
- z-index: 1000;
+ z-index: 10000;
}
.hvac-trainer-menu {
position: relative;
- z-index: 1001;
+ z-index: 10001;
+}
+
+/* Menu items need relative positioning for absolute submenus */
+.hvac-trainer-menu .menu-item.has-children {
+ position: relative;
+}
+
+/* Ensure submenus appear above everything */
+.hvac-trainer-menu .sub-menu {
+ z-index: 99999 !important;
+ position: absolute !important;
}
\ No newline at end of file
diff --git a/assets/js/hvac-dashboard.js b/assets/js/hvac-dashboard.js
index 6d9c07f9..78a38dd4 100644
--- a/assets/js/hvac-dashboard.js
+++ b/assets/js/hvac-dashboard.js
@@ -9,6 +9,7 @@
// Initialize the dashboard when DOM is ready
$(document).ready(function() {
initEventFilters();
+ initPaginationHandlers();
});
/**
@@ -108,4 +109,95 @@
return newQueryString.length > 0 ? urlBase + '?' + newQueryString : urlBase;
}
+ /**
+ * Initialize pagination handlers for events table
+ */
+ function initPaginationHandlers() {
+ // Delegate click handlers to handle dynamically loaded content
+ $(document).on('click', '.hvac-events-table-wrapper .pagination-links a', function(e) {
+ e.preventDefault();
+
+ const $link = $(this);
+ const page = $link.data('page');
+
+ if (!page || $link.hasClass('disabled')) {
+ return;
+ }
+
+ loadEventsPage(page);
+ });
+
+ // Handle page number input
+ $(document).on('keypress', '.hvac-events-table-wrapper .current-page', function(e) {
+ if (e.which === 13) { // Enter key
+ e.preventDefault();
+ const page = parseInt($(this).val());
+ const maxPage = parseInt($(this).closest('.pagination-links').find('.total-pages').text());
+
+ if (page && page > 0 && page <= maxPage) {
+ loadEventsPage(page);
+ }
+ }
+ });
+ }
+
+ /**
+ * Load a specific page of events
+ */
+ function loadEventsPage(page) {
+ const $eventsTableWrapper = $('.hvac-events-table-wrapper');
+
+ // Get current filter status
+ const $activeFilter = $('.hvac-event-filters .hvac-filter-active');
+ const filterUrl = $activeFilter.length ? $activeFilter.attr('href') : '';
+ const url = new URL(filterUrl || window.location.href, window.location.origin);
+ const status = url.searchParams.get('event_status') || 'all';
+
+ // Show loading indicator
+ $eventsTableWrapper.append('
Loading page...
');
+
+ // Make AJAX request to get the requested page
+ $.ajax({
+ url: hvac_dashboard.ajax_url,
+ type: 'POST',
+ data: {
+ action: 'hvac_filter_events',
+ status: status,
+ page: page,
+ nonce: hvac_dashboard.nonce
+ },
+ success: function(response) {
+ if (response.success) {
+ // Replace the table HTML with the new page
+ $eventsTableWrapper.html(response.data.html);
+
+ // Update the URL with the new page number
+ if (history.pushState) {
+ let newUrl = window.location.href;
+ if (page > 1) {
+ newUrl = addURLParameter(newUrl, 'paged', page);
+ } else {
+ newUrl = removeURLParameter(newUrl, 'paged');
+ }
+ window.history.pushState({ path: newUrl }, '', newUrl);
+ }
+
+ // Scroll to top of table
+ $('html, body').animate({
+ scrollTop: $eventsTableWrapper.offset().top - 100
+ }, 300);
+ } else {
+ // Show error message
+ $eventsTableWrapper.find('.hvac-loading').remove();
+ $eventsTableWrapper.append('Error loading page: ' + response.data.message + '
');
+ }
+ },
+ error: function() {
+ // Show error message
+ $eventsTableWrapper.find('.hvac-loading').remove();
+ $eventsTableWrapper.append('Error communicating with server.
');
+ }
+ });
+ }
+
})(jQuery);
\ No newline at end of file
diff --git a/assets/js/hvac-menu-system.js b/assets/js/hvac-menu-system.js
index c87637dc..a22171cb 100644
--- a/assets/js/hvac-menu-system.js
+++ b/assets/js/hvac-menu-system.js
@@ -6,6 +6,10 @@
jQuery(function($) {
'use strict';
+ // COMPLETELY DISABLED SYSTEM - DO NOT USE
+ console.log('HVAC Menu: OLD SYSTEM COMPLETELY DISABLED - Use hvac-navigation-robust.js instead');
+ return; // EXIT IMMEDIATELY
+
// Check if jQuery is available
if (typeof jQuery === 'undefined') {
console.error('HVAC Menu: jQuery is not available');
@@ -15,10 +19,12 @@ jQuery(function($) {
console.log('HVAC Menu: jQuery loaded, version:', $.fn.jquery);
/**
- * Initialize menu system
+ * Initialize menu system - DISABLED: Replaced by hvac-navigation-robust.js
*/
function initHVACMenu() {
- console.log('HVAC Menu: Initializing menu system');
+ // COMPLETELY DISABLED to prevent conflicts with hvac-navigation-robust.js
+ console.log('HVAC Menu: DISABLED - All functionality moved to hvac-navigation-robust.js');
+ return;
const $menu = $('.hvac-trainer-menu');
@@ -49,6 +55,12 @@ jQuery(function($) {
* Handle hamburger menu toggle
*/
function handleHamburgerMenu() {
+ // Check if robust navigation has taken control
+ if (window.hvacRobustNavigationActive) {
+ console.log('HVAC Menu: Robust navigation active, skipping original hamburger setup');
+ return;
+ }
+
const $hamburger = $('#hvac-hamburger-menu');
const $menu = $('#hvac-trainer-menu');
diff --git a/assets/js/hvac-navigation-robust.js b/assets/js/hvac-navigation-robust.js
index 7629c3eb..17d1b0a8 100644
--- a/assets/js/hvac-navigation-robust.js
+++ b/assets/js/hvac-navigation-robust.js
@@ -2,10 +2,14 @@
* HVAC Navigation - Robust & Simple
* Ensures navigation dropdowns work reliably for all users
*
- * @version 1.0.0
+ * @version 1.0.1
* @since 2025-08-21
*/
+// CRITICAL: Disable the original menu system IMMEDIATELY to prevent conflicts
+// This must be set OUTSIDE of jQuery ready to ensure it's available first
+window.hvacRobustNavigationActive = true;
+
jQuery(document).ready(function($) {
'use strict';
@@ -18,8 +22,12 @@ jQuery(document).ready(function($) {
console.log('HVAC Navigation: Found', $menuToggles.length, 'menu toggles');
+ // AGGRESSIVE: Remove ALL competing handlers first
+ $menuToggles.off(); // Remove all handlers
+ $menuToggles.unbind(); // jQuery < 3.0 compatibility
+
// Add click handlers to dropdown toggles
- $menuToggles.off('click.hvacRobust').on('click.hvacRobust', function(e) {
+ $menuToggles.on('click.hvacRobust', function(e) {
e.preventDefault();
e.stopPropagation();
@@ -32,11 +40,17 @@ jQuery(document).ready(function($) {
// Close other open menus at the same level
$this.closest('ul').find('> .menu-item.open').not($menuItem).removeClass('open');
- // Toggle this menu
- $menuItem.toggleClass('open');
+ // Toggle this menu - FORCE the class change
+ if ($menuItem.hasClass('open')) {
+ $menuItem.removeClass('open');
+ console.log('HVAC Navigation: Menu closed');
+ } else {
+ $menuItem.addClass('open');
+ console.log('HVAC Navigation: Menu opened');
+ }
- // Log the state
- console.log('HVAC Navigation: Menu is now', $menuItem.hasClass('open') ? 'open' : 'closed');
+ // Double-check the state
+ console.log('HVAC Navigation: Final state:', $menuItem.hasClass('open') ? 'open' : 'closed');
});
// Ensure hamburger menu works
@@ -46,7 +60,14 @@ jQuery(document).ready(function($) {
if ($hamburger.length && $menu.length) {
console.log('HVAC Navigation: Setting up hamburger menu');
- $hamburger.off('click.hvacRobust').on('click.hvacRobust', function(e) {
+ // CRITICAL: Aggressively remove all existing handlers to prevent conflicts
+ // This ensures only our handler runs
+ $hamburger.off(); // Remove ALL handlers
+ $hamburger.unbind(); // jQuery < 3.0 compatibility
+ $hamburger.removeClass('active'); // Reset state
+
+ // Add only our handler with immediate effect
+ $hamburger.on('click.hvacRobust', function(e) {
e.preventDefault();
e.stopPropagation();
@@ -61,6 +82,26 @@ jQuery(document).ready(function($) {
console.log('HVAC Navigation: Menu is now', isOpen ? 'active' : 'inactive');
});
+
+ // AGGRESSIVE: Monitor and remove conflicting handlers continuously
+ setInterval(function() {
+ const events = $._data($hamburger[0], 'events');
+ if (events && events.click && events.click.length > 1) {
+ console.log('HVAC Navigation: Removing conflicting handlers');
+ // Keep only our namespaced handler
+ $hamburger.off('click');
+ $hamburger.on('click.hvacRobust', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ console.log('HVAC Navigation: Hamburger clicked (monitored)');
+ $hamburger.toggleClass('active');
+ $menu.toggleClass('active');
+ const isOpen = $menu.hasClass('active');
+ $hamburger.attr('aria-expanded', isOpen);
+ console.log('HVAC Navigation: Menu is now', isOpen ? 'active' : 'inactive');
+ });
+ }
+ }, 1000);
}
// Close menu when clicking outside (on mobile)
diff --git a/includes/class-hvac-community-events.php b/includes/class-hvac-community-events.php
index cff1ca55..f50848b7 100644
--- a/includes/class-hvac-community-events.php
+++ b/includes/class-hvac-community-events.php
@@ -507,11 +507,8 @@ class HVAC_Community_Events {
// Community login shortcode - initialize Login_Handler to register the shortcode
new \HVAC_Community_Events\Community\Login_Handler();
- // Dashboard shortcode
- add_shortcode('hvac_dashboard', array($this, 'render_dashboard'));
-
- // Master Dashboard shortcode
- add_shortcode('hvac_master_dashboard', array($this, 'render_master_dashboard'));
+ // Dashboard shortcodes moved to class-hvac-shortcodes.php for centralization
+ // The shortcodes are now registered in the centralized shortcode manager
// Add the event summary shortcode
add_shortcode('hvac_event_summary', array($this, 'render_event_summary'));
diff --git a/includes/class-hvac-dashboard-data.php b/includes/class-hvac-dashboard-data.php
index a1ee524a..7caacda7 100644
--- a/includes/class-hvac-dashboard-data.php
+++ b/includes/class-hvac-dashboard-data.php
@@ -46,17 +46,37 @@ class HVAC_Dashboard_Data {
public function get_total_events_count() {
global $wpdb;
- // Use direct database query to avoid TEC query hijacking
- $count = $wpdb->get_var( $wpdb->prepare(
- "SELECT COUNT(*) FROM {$wpdb->posts}
- WHERE post_type = %s
- AND post_author = %d
- AND post_status IN ('publish', 'future', 'draft', 'pending', 'private')",
- Tribe__Events__Main::POSTTYPE,
- $this->user_id
- ) );
-
- return (int) $count;
+ try {
+ // Cache key based on user ID
+ $cache_key = 'hvac_dashboard_total_events_' . $this->user_id;
+ $count = wp_cache_get( $cache_key, 'hvac_dashboard' );
+
+ if ( false === $count ) {
+ // Use direct database query to avoid TEC query hijacking
+ $count = $wpdb->get_var( $wpdb->prepare(
+ "SELECT COUNT(*) FROM {$wpdb->posts}
+ WHERE post_type = %s
+ AND post_author = %d
+ AND post_status IN ('publish', 'future', 'draft', 'pending', 'private')",
+ Tribe__Events__Main::POSTTYPE,
+ $this->user_id
+ ) );
+
+ // Handle database errors
+ if ( $wpdb->last_error ) {
+ $this->log_error( 'Database error in get_total_events_count', $wpdb->last_error );
+ return 0;
+ }
+
+ // Cache for 1 hour
+ wp_cache_set( $cache_key, $count, 'hvac_dashboard', HOUR_IN_SECONDS );
+ }
+
+ return (int) $count;
+ } catch ( Exception $e ) {
+ $this->log_error( 'Exception in get_total_events_count', $e->getMessage() );
+ return 0;
+ }
}
/**
@@ -66,20 +86,30 @@ class HVAC_Dashboard_Data {
*/
public function get_upcoming_events_count() {
global $wpdb;
- $today = date( 'Y-m-d H:i:s' );
- // Use direct database query to avoid TEC query hijacking
- $count = $wpdb->get_var( $wpdb->prepare(
- "SELECT COUNT(*) FROM {$wpdb->posts} p
- LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_EventStartDate'
- WHERE p.post_type = %s
- AND p.post_author = %d
- AND p.post_status IN ('publish', 'future')
- AND (pm.meta_value >= %s OR pm.meta_value IS NULL)",
- Tribe__Events__Main::POSTTYPE,
- $this->user_id,
- $today
- ) );
+ // Cache key based on user ID
+ $cache_key = 'hvac_dashboard_upcoming_events_' . $this->user_id;
+ $count = wp_cache_get( $cache_key, 'hvac_dashboard' );
+
+ if ( false === $count ) {
+ $today = date( 'Y-m-d H:i:s' );
+
+ // Use direct database query to avoid TEC query hijacking
+ $count = $wpdb->get_var( $wpdb->prepare(
+ "SELECT COUNT(*) FROM {$wpdb->posts} p
+ LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_EventStartDate'
+ WHERE p.post_type = %s
+ AND p.post_author = %d
+ AND p.post_status IN ('publish', 'future')
+ AND (pm.meta_value >= %s OR pm.meta_value IS NULL)",
+ Tribe__Events__Main::POSTTYPE,
+ $this->user_id,
+ $today
+ ) );
+
+ // Cache for 15 minutes (shorter for time-sensitive data)
+ wp_cache_set( $cache_key, $count, 'hvac_dashboard', 15 * MINUTE_IN_SECONDS );
+ }
return (int) $count;
}
@@ -91,20 +121,30 @@ class HVAC_Dashboard_Data {
*/
public function get_past_events_count() {
global $wpdb;
- $today = date( 'Y-m-d H:i:s' );
- // Use direct database query to avoid TEC query hijacking
- $count = $wpdb->get_var( $wpdb->prepare(
- "SELECT COUNT(*) FROM {$wpdb->posts} p
- LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_EventEndDate'
- WHERE p.post_type = %s
- AND p.post_author = %d
- AND p.post_status IN ('publish', 'private')
- AND pm.meta_value < %s",
- Tribe__Events__Main::POSTTYPE,
- $this->user_id,
- $today
- ) );
+ // Cache key based on user ID
+ $cache_key = 'hvac_dashboard_past_events_' . $this->user_id;
+ $count = wp_cache_get( $cache_key, 'hvac_dashboard' );
+
+ if ( false === $count ) {
+ $today = date( 'Y-m-d H:i:s' );
+
+ // Use direct database query to avoid TEC query hijacking
+ $count = $wpdb->get_var( $wpdb->prepare(
+ "SELECT COUNT(*) FROM {$wpdb->posts} p
+ LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_EventEndDate'
+ WHERE p.post_type = %s
+ AND p.post_author = %d
+ AND p.post_status IN ('publish', 'private')
+ AND pm.meta_value < %s",
+ Tribe__Events__Main::POSTTYPE,
+ $this->user_id,
+ $today
+ ) );
+
+ // Cache for 1 hour
+ wp_cache_set( $cache_key, $count, 'hvac_dashboard', HOUR_IN_SECONDS );
+ }
return (int) $count;
}
@@ -117,41 +157,51 @@ class HVAC_Dashboard_Data {
public function get_total_tickets_sold() {
global $wpdb;
- // Count TEC Commerce attendees
- $tec_commerce_count = $wpdb->get_var( $wpdb->prepare(
- "SELECT COUNT(*) FROM {$wpdb->posts} p
- INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_tec_tickets_commerce_event'
- WHERE p.post_type = %s
- AND pm.meta_value IN (
- SELECT ID FROM {$wpdb->posts}
- WHERE post_type = %s
- AND post_author = %d
- AND post_status IN ('publish', 'private')
- )",
- 'tec_tc_attendee',
- 'tribe_events',
- $this->user_id
- ) );
+ // Cache key based on user ID
+ $cache_key = 'hvac_dashboard_tickets_sold_' . $this->user_id;
+ $total = wp_cache_get( $cache_key, 'hvac_dashboard' );
- // Count legacy Tribe PayPal attendees
- $tribe_tpp_count = $wpdb->get_var( $wpdb->prepare(
- "SELECT COUNT(*) FROM {$wpdb->posts} p
- INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_tribe_tpp_event'
- WHERE p.post_type = %s
- AND pm.meta_value IN (
- SELECT ID FROM {$wpdb->posts}
- WHERE post_type = %s
- AND post_author = %d
- AND post_status IN ('publish', 'private')
- )",
- 'tribe_tpp_attendees',
- 'tribe_events',
- $this->user_id
- ) );
+ if ( false === $total ) {
+ // Count TEC Commerce attendees
+ $tec_commerce_count = $wpdb->get_var( $wpdb->prepare(
+ "SELECT COUNT(*) FROM {$wpdb->posts} p
+ INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_tec_tickets_commerce_event'
+ WHERE p.post_type = %s
+ AND pm.meta_value IN (
+ SELECT ID FROM {$wpdb->posts}
+ WHERE post_type = %s
+ AND post_author = %d
+ AND post_status IN ('publish', 'private')
+ )",
+ 'tec_tc_attendee',
+ 'tribe_events',
+ $this->user_id
+ ) );
+
+ // Count legacy Tribe PayPal attendees
+ $tribe_tpp_count = $wpdb->get_var( $wpdb->prepare(
+ "SELECT COUNT(*) FROM {$wpdb->posts} p
+ INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_tribe_tpp_event'
+ WHERE p.post_type = %s
+ AND pm.meta_value IN (
+ SELECT ID FROM {$wpdb->posts}
+ WHERE post_type = %s
+ AND post_author = %d
+ AND post_status IN ('publish', 'private')
+ )",
+ 'tribe_tpp_attendees',
+ 'tribe_events',
+ $this->user_id
+ ) );
+
+ // Note: RSVP attendees are not counted as "tickets sold" since they are free registrations
+ $total = (int) ($tec_commerce_count + $tribe_tpp_count);
+
+ // Cache for 30 minutes
+ wp_cache_set( $cache_key, $total, 'hvac_dashboard', 30 * MINUTE_IN_SECONDS );
+ }
- // Note: RSVP attendees are not counted as "tickets sold" since they are free registrations
-
- return (int) ($tec_commerce_count + $tribe_tpp_count);
+ return $total;
}
/**
@@ -162,8 +212,13 @@ class HVAC_Dashboard_Data {
public function get_total_revenue() {
global $wpdb;
- // Get TEC Commerce revenue
- $tec_commerce_revenue = $wpdb->get_var( $wpdb->prepare(
+ // Cache key based on user ID
+ $cache_key = 'hvac_dashboard_revenue_' . $this->user_id;
+ $total_revenue = wp_cache_get( $cache_key, 'hvac_dashboard' );
+
+ if ( false === $total_revenue ) {
+ // Get TEC Commerce revenue
+ $tec_commerce_revenue = $wpdb->get_var( $wpdb->prepare(
"SELECT SUM(CAST(pm_price.meta_value AS DECIMAL(10,2)))
FROM {$wpdb->posts} p
INNER JOIN {$wpdb->postmeta} pm_event ON p.ID = pm_event.post_id AND pm_event.meta_key = '_tec_tickets_commerce_event'
@@ -207,9 +262,15 @@ class HVAC_Dashboard_Data {
$this->user_id
) );
- // Note: RSVP attendees typically don't have revenue (free tickets)
+ // Note: RSVP attendees typically don't have revenue (free tickets)
+
+ $total_revenue = (float) (($tec_commerce_revenue ?: 0.00) + ($tribe_tpp_revenue ?: 0.00));
+
+ // Cache for 30 minutes
+ wp_cache_set( $cache_key, $total_revenue, 'hvac_dashboard', 30 * MINUTE_IN_SECONDS );
+ }
- return (float) (($tec_commerce_revenue ?: 0.00) + ($tribe_tpp_revenue ?: 0.00));
+ return $total_revenue;
}
/**
@@ -421,6 +482,41 @@ class HVAC_Dashboard_Data {
);
}
+ /**
+ * Clear all cached data for this user
+ *
+ * This should be called whenever data changes (event created/updated/deleted)
+ */
+ public function clear_cache() {
+ $cache_keys = array(
+ 'hvac_dashboard_total_events_' . $this->user_id,
+ 'hvac_dashboard_upcoming_events_' . $this->user_id,
+ 'hvac_dashboard_past_events_' . $this->user_id,
+ 'hvac_dashboard_tickets_sold_' . $this->user_id,
+ 'hvac_dashboard_revenue_' . $this->user_id,
+ );
+
+ foreach ( $cache_keys as $key ) {
+ wp_cache_delete( $key, 'hvac_dashboard' );
+ }
+ }
+
+ /**
+ * Log errors for debugging
+ *
+ * @param string $message Error message
+ * @param mixed $data Additional data to log
+ */
+ private function log_error( $message, $data = null ) {
+ if ( defined( 'WP_DEBUG' ) && WP_DEBUG && defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
+ $log_message = '[HVAC Dashboard Data] ' . $message;
+ if ( $data ) {
+ $log_message .= ' | Data: ' . print_r( $data, true );
+ }
+ error_log( $log_message );
+ }
+ }
+
/**
* Count the number of attendees for an event by querying attendee posts
*
diff --git a/includes/class-hvac-dashboard.php b/includes/class-hvac-dashboard.php
index 1927b858..88beda84 100644
--- a/includes/class-hvac-dashboard.php
+++ b/includes/class-hvac-dashboard.php
@@ -73,8 +73,10 @@ class HVAC_Dashboard {
private function get_dashboard_content() {
$user_id = get_current_user_id();
- // Include dashboard data class
- require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-dashboard-data.php';
+ // Get dashboard data instance (class is autoloaded)
+ if (!class_exists('HVAC_Dashboard_Data')) {
+ require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-dashboard-data.php';
+ }
$dashboard_data = new HVAC_Dashboard_Data($user_id);
// Get data
@@ -197,8 +199,8 @@ class HVAC_Dashboard {
$ |
Edit |
Summary
@@ -221,18 +223,31 @@ class HVAC_Dashboard {
* Handle AJAX request for filtered events table
*/
public function ajax_filter_events() {
- // Check nonce
- if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_dashboard_nonce')) {
- wp_send_json_error(array('message' => 'Security check failed.'));
- return;
- }
-
- // Get current user ID
- $user_id = get_current_user_id();
- if (!$user_id || !current_user_can('view_hvac_dashboard')) {
- wp_send_json_error(array('message' => 'Access denied.'));
- return;
- }
+ try {
+ // Check nonce
+ if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_dashboard_nonce')) {
+ $this->log_error('Dashboard AJAX: Nonce verification failed', $_POST);
+ wp_send_json_error(array('message' => 'Security check failed.'));
+ return;
+ }
+
+ // Get current user ID
+ $user_id = get_current_user_id();
+ if (!$user_id) {
+ $this->log_error('Dashboard AJAX: User not logged in');
+ wp_send_json_error(array('message' => 'Please log in to continue.'));
+ return;
+ }
+
+ // Check user role (not capability)
+ $user = wp_get_current_user();
+ $has_trainer_role = in_array('hvac_trainer', $user->roles) || in_array('hvac_master_trainer', $user->roles);
+
+ if (!$has_trainer_role && !current_user_can('manage_options')) {
+ $this->log_error('Dashboard AJAX: Insufficient permissions', array('user_id' => $user_id, 'roles' => $user->roles));
+ wp_send_json_error(array('message' => 'Access denied.'));
+ return;
+ }
// Get all filters and parameters
$args = array(
@@ -246,8 +261,10 @@ class HVAC_Dashboard {
'date_to' => isset($_POST['date_to']) ? sanitize_text_field($_POST['date_to']) : '',
);
- // Include dashboard data class
- require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-dashboard-data.php';
+ // Get dashboard data instance (class is autoloaded)
+ if (!class_exists('HVAC_Dashboard_Data')) {
+ require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-dashboard-data.php';
+ }
$dashboard_data = new HVAC_Dashboard_Data($user_id);
// Get filtered events data
@@ -412,6 +429,30 @@ class HVAC_Dashboard {
'pagination' => $pagination,
'args' => $args
));
+
+ } catch (Exception $e) {
+ $this->log_error('Dashboard AJAX Exception', array(
+ 'message' => $e->getMessage(),
+ 'trace' => $e->getTraceAsString()
+ ));
+ wp_send_json_error(array('message' => 'An error occurred while loading events.'));
+ }
+ }
+
+ /**
+ * Log errors for debugging
+ *
+ * @param string $message Error message
+ * @param mixed $data Additional data to log
+ */
+ private function log_error($message, $data = null) {
+ if (defined('WP_DEBUG') && WP_DEBUG && defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
+ $log_message = '[HVAC Dashboard] ' . $message;
+ if ($data) {
+ $log_message .= ' | Data: ' . print_r($data, true);
+ }
+ error_log($log_message);
+ }
}
/**
diff --git a/includes/class-hvac-menu-system.php b/includes/class-hvac-menu-system.php
index 4504597b..3c181005 100644
--- a/includes/class-hvac-menu-system.php
+++ b/includes/class-hvac-menu-system.php
@@ -50,6 +50,11 @@ class HVAC_Menu_System {
add_action('init', array($this, 'register_menu_locations'));
add_action('wp', array($this, 'setup_trainer_menu'));
add_action('wp_enqueue_scripts', array($this, 'enqueue_menu_styles'));
+
+ // CRITICAL FIX: Also enqueue on wp_head to catch late-loaded templates
+ // This ensures scripts load even when templates are loaded via template_include filter
+ add_action('wp_head', array($this, 'ensure_scripts_loaded'), 1);
+
add_filter('wp_nav_menu_items', array($this, 'add_logout_to_menu'), 10, 2);
}
@@ -161,6 +166,13 @@ class HVAC_Menu_System {
return $this->render_trainer_menu();
}
+ /**
+ * Static method for templates calling render_navigation()
+ */
+ public static function render_navigation() {
+ return self::instance()->render_trainer_menu();
+ }
+
/**
* Get menu structure based on user capabilities
*/
@@ -337,21 +349,63 @@ class HVAC_Menu_System {
* Enqueue menu styles and scripts
*/
public function enqueue_menu_styles() {
- if ($this->is_trainer_page() && $this->user_can_access_trainer_area()) {
- wp_enqueue_style(
- 'hvac-menu-system',
- HVAC_PLUGIN_URL . 'assets/css/hvac-menu-system.css',
- array(),
- HVAC_PLUGIN_VERSION
- );
-
- wp_enqueue_script(
- 'hvac-menu-system',
- $this->get_compatible_script_path('hvac-menu-system'),
- array('jquery'),
- HVAC_PLUGIN_VERSION,
- true
- );
+ // AGGRESSIVE: Enqueue on ALL pages to prevent missing JavaScript issues
+ // The menu is only rendered when user has proper access, so this is safe
+
+ wp_enqueue_style(
+ 'hvac-menu-system',
+ HVAC_PLUGIN_URL . 'assets/css/hvac-menu-system.css',
+ array(),
+ HVAC_PLUGIN_VERSION
+ );
+
+ // CRITICAL: Enqueue JavaScript on ALL pages to ensure dropdowns work
+ // The JavaScript only activates when menu elements are present
+ wp_enqueue_script(
+ 'hvac-navigation-robust',
+ HVAC_PLUGIN_URL . 'assets/js/hvac-navigation-robust.js',
+ array('jquery'),
+ HVAC_PLUGIN_VERSION,
+ true
+ );
+ }
+
+ /**
+ * Ensure scripts are loaded even for late-loaded templates
+ * This fixes the issue where dashboard pages loaded via template_include don't get scripts
+ */
+ public function ensure_scripts_loaded() {
+ // Check if we're on a trainer page that needs the menu
+ if (!is_user_logged_in()) {
+ return;
+ }
+
+ // ALWAYS output the scripts directly in wp_head for dashboard pages
+ // This ensures they load regardless of enqueue status
+ global $wp;
+ $current_url = home_url(add_query_arg(array(), $wp->request));
+ $is_trainer_page = strpos($current_url, '/trainer/') !== false || strpos($current_url, '/master-trainer/') !== false;
+
+ if ($is_trainer_page) {
+ // Only add direct scripts if they're not already enqueued
+ if (!wp_script_is('hvac-navigation-robust', 'enqueued')) {
+ ?>
+
+
+ version
);
- // Load simple navigation styles with high priority
- wp_enqueue_style(
- 'hvac-navigation-simple',
- HVAC_PLUGIN_URL . 'assets/css/hvac-navigation-simple.css',
- array('hvac-design-system', 'hvac-components'),
- $this->version
- );
+ // Navigation styles now handled by consolidated core CSS
+ // Removed hvac-navigation-simple.css to prevent CSS conflicts
+ // The consolidated core CSS has proper flex-direction: row declarations
- // Always load fixed core bundle
+ // Always load full core bundle (contains navigation CSS)
wp_enqueue_style(
'hvac-consolidated-core',
- HVAC_PLUGIN_URL . 'assets/css/hvac-consolidated-core-fixed.css',
+ HVAC_PLUGIN_URL . 'assets/css/hvac-consolidated-core.css',
array('hvac-design-system', 'hvac-components'),
$this->version
);
diff --git a/includes/class-hvac-welcome-popup.php b/includes/class-hvac-welcome-popup.php
index c95a2665..9c876467 100644
--- a/includes/class-hvac-welcome-popup.php
+++ b/includes/class-hvac-welcome-popup.php
@@ -51,6 +51,10 @@ class HVAC_Welcome_Popup {
* Enqueue popup assets on dashboard page
*/
public function enqueue_assets() {
+ // TEMPORARY FIX: Disable welcome popup to test dropdown interference
+ // The welcome popup appears to be blocking dropdown clicks on dashboard
+ return;
+
// Only load on trainer dashboard page
if (!$this->is_dashboard_page()) {
return;
diff --git a/templates/page-trainer-dashboard.php b/templates/page-trainer-dashboard.php
index f16e2e27..bb48d9b9 100644
--- a/templates/page-trainer-dashboard.php
+++ b/templates/page-trainer-dashboard.php
@@ -10,7 +10,6 @@ define('HVAC_IN_PAGE_TEMPLATE', true);
get_header();
?>
-
roles) || in_array('hvac_master_trainer', $user->roles);
+
+ if ( ! $has_trainer_role && ! current_user_can( 'manage_options' ) ) {
// Show access denied message instead of redirect to prevent loops
?>
-
@@ -87,8 +56,10 @@ get_header();
// Get the current user ID
$user_id = get_current_user_id();
- // Include and instantiate the dashboard data class
- require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-dashboard-data.php';
+ // Get dashboard data instance (class is autoloaded)
+ if (!class_exists('HVAC_Dashboard_Data')) {
+ require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-dashboard-data.php';
+ }
$dashboard_data = new HVAC_Dashboard_Data( $user_id );
// Fetch data
@@ -369,71 +340,6 @@ get_header();
-
-
diff --git a/templates/page-trainer-organizers-list.php b/templates/page-trainer-organizers-list.php
index 022cfc5e..7854c757 100644
--- a/templates/page-trainer-organizers-list.php
+++ b/templates/page-trainer-organizers-list.php
@@ -17,6 +17,14 @@ get_header();
HVAC_Menu_System::instance()->render_trainer_menu();
}
?>
+
+ render_breadcrumbs();
+ }
+ ?>
+
render_trainer_menu();
}
?>
+
+ render_breadcrumbs();
+ }
+ ?>
+
|