fix: comprehensive dashboard fixes and improvements
- Fixed critical security vulnerability with incorrect capability checks - Fixed hardcoded redirect path from /community-login/ to /training-login/ - Moved dashboard shortcode registration to centralized location - Fixed duplicate class loading with proper singleton checks - Fixed incorrect edit URLs in dashboard - Removed debug HTML comments from production templates - Moved inline CSS to external stylesheets for better maintainability - Added caching mechanism for dashboard statistics queries (1 hour cache) - Implemented pagination JavaScript handlers for AJAX navigation - Added comprehensive error handling and logging throughout - Fixed role-based access control (checking roles not capabilities) - Improved performance with cached database queries
This commit is contained in:
parent
845451866f
commit
8be49ad5a9
18 changed files with 620 additions and 247 deletions
|
|
@ -109,7 +109,9 @@
|
||||||
"Bash(scripts/fix-production-issues.sh:*)",
|
"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!)",
|
"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__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": []
|
"deny": []
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2871,9 +2871,19 @@ body.hvac-modal-open {
|
||||||
* WordPress-compliant navigation styling
|
* WordPress-compliant navigation styling
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Increase specificity to override theme styles */
|
/* Dashboard navigation - NO container styling */
|
||||||
.hvac-page-wrapper .hvac-trainer-menu-wrapper,
|
.hvac-trainer-dashboard-page .hvac-trainer-menu-wrapper {
|
||||||
.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;
|
background: #ffffff !important;
|
||||||
border-bottom: 1px solid #e0e0e0 !important;
|
border-bottom: 1px solid #e0e0e0 !important;
|
||||||
margin-bottom: 20px !important;
|
margin-bottom: 20px !important;
|
||||||
|
|
|
||||||
|
|
@ -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
|
Event Edit Form Fixes
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,19 @@
|
||||||
* WordPress-compliant navigation styling
|
* WordPress-compliant navigation styling
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Increase specificity to override theme styles */
|
/* Dashboard navigation - NO container styling */
|
||||||
.hvac-page-wrapper .hvac-trainer-menu-wrapper,
|
.hvac-trainer-dashboard-page .hvac-trainer-menu-wrapper {
|
||||||
.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;
|
background: #ffffff !important;
|
||||||
border-bottom: 1px solid #e0e0e0 !important;
|
border-bottom: 1px solid #e0e0e0 !important;
|
||||||
margin-bottom: 20px !important;
|
margin-bottom: 20px !important;
|
||||||
|
|
@ -113,7 +123,7 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
z-index: 9999;
|
z-index: 99999 !important;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@
|
||||||
padding: 8px 0 !important;
|
padding: 8px 0 !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
list-style: none !important;
|
list-style: none !important;
|
||||||
z-index: 9999 !important;
|
z-index: 99999 !important;
|
||||||
|
|
||||||
/* Hidden by default */
|
/* Hidden by default */
|
||||||
display: none !important;
|
display: none !important;
|
||||||
|
|
@ -140,12 +140,18 @@
|
||||||
/* Ensure proper z-index layering */
|
/* Ensure proper z-index layering */
|
||||||
.hvac-trainer-menu-wrapper {
|
.hvac-trainer-menu-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1000;
|
z-index: 10000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hvac-trainer-menu {
|
.hvac-trainer-menu {
|
||||||
position: relative;
|
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 */
|
/* Override any conflicting styles */
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,15 @@
|
||||||
display: block !important;
|
display: block !important;
|
||||||
max-height: 500px !important;
|
max-height: 500px !important;
|
||||||
padding: 8px 0 !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 */
|
/* Submenu items */
|
||||||
|
|
@ -124,6 +133,19 @@
|
||||||
padding-left: 35px;
|
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 */
|
/* Force visibility on mobile when menu is active */
|
||||||
@media (max-width: 992px) {
|
@media (max-width: 992px) {
|
||||||
.hvac-trainer-menu {
|
.hvac-trainer-menu {
|
||||||
|
|
@ -169,10 +191,21 @@
|
||||||
/* Ensure proper z-index stacking */
|
/* Ensure proper z-index stacking */
|
||||||
.hvac-trainer-nav {
|
.hvac-trainer-nav {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1000;
|
z-index: 10000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hvac-trainer-menu {
|
.hvac-trainer-menu {
|
||||||
position: relative;
|
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;
|
||||||
}
|
}
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
// Initialize the dashboard when DOM is ready
|
// Initialize the dashboard when DOM is ready
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
initEventFilters();
|
initEventFilters();
|
||||||
|
initPaginationHandlers();
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -108,4 +109,95 @@
|
||||||
return newQueryString.length > 0 ? urlBase + '?' + newQueryString : urlBase;
|
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('<div class="hvac-loading">Loading page...</div>');
|
||||||
|
|
||||||
|
// 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('<div class="hvac-error">Error loading page: ' + response.data.message + '</div>');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
// Show error message
|
||||||
|
$eventsTableWrapper.find('.hvac-loading').remove();
|
||||||
|
$eventsTableWrapper.append('<div class="hvac-error">Error communicating with server.</div>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
|
|
@ -6,6 +6,10 @@
|
||||||
jQuery(function($) {
|
jQuery(function($) {
|
||||||
'use strict';
|
'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
|
// Check if jQuery is available
|
||||||
if (typeof jQuery === 'undefined') {
|
if (typeof jQuery === 'undefined') {
|
||||||
console.error('HVAC Menu: jQuery is not available');
|
console.error('HVAC Menu: jQuery is not available');
|
||||||
|
|
@ -15,10 +19,12 @@ jQuery(function($) {
|
||||||
console.log('HVAC Menu: jQuery loaded, version:', $.fn.jquery);
|
console.log('HVAC Menu: jQuery loaded, version:', $.fn.jquery);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize menu system
|
* Initialize menu system - DISABLED: Replaced by hvac-navigation-robust.js
|
||||||
*/
|
*/
|
||||||
function initHVACMenu() {
|
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');
|
const $menu = $('.hvac-trainer-menu');
|
||||||
|
|
||||||
|
|
@ -49,6 +55,12 @@ jQuery(function($) {
|
||||||
* Handle hamburger menu toggle
|
* Handle hamburger menu toggle
|
||||||
*/
|
*/
|
||||||
function handleHamburgerMenu() {
|
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 $hamburger = $('#hvac-hamburger-menu');
|
||||||
const $menu = $('#hvac-trainer-menu');
|
const $menu = $('#hvac-trainer-menu');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,14 @@
|
||||||
* HVAC Navigation - Robust & Simple
|
* HVAC Navigation - Robust & Simple
|
||||||
* Ensures navigation dropdowns work reliably for all users
|
* Ensures navigation dropdowns work reliably for all users
|
||||||
*
|
*
|
||||||
* @version 1.0.0
|
* @version 1.0.1
|
||||||
* @since 2025-08-21
|
* @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($) {
|
jQuery(document).ready(function($) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
|
@ -18,8 +22,12 @@ jQuery(document).ready(function($) {
|
||||||
|
|
||||||
console.log('HVAC Navigation: Found', $menuToggles.length, 'menu toggles');
|
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
|
// Add click handlers to dropdown toggles
|
||||||
$menuToggles.off('click.hvacRobust').on('click.hvacRobust', function(e) {
|
$menuToggles.on('click.hvacRobust', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
|
@ -32,11 +40,17 @@ jQuery(document).ready(function($) {
|
||||||
// Close other open menus at the same level
|
// Close other open menus at the same level
|
||||||
$this.closest('ul').find('> .menu-item.open').not($menuItem).removeClass('open');
|
$this.closest('ul').find('> .menu-item.open').not($menuItem).removeClass('open');
|
||||||
|
|
||||||
// Toggle this menu
|
// Toggle this menu - FORCE the class change
|
||||||
$menuItem.toggleClass('open');
|
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
|
// Double-check the state
|
||||||
console.log('HVAC Navigation: Menu is now', $menuItem.hasClass('open') ? 'open' : 'closed');
|
console.log('HVAC Navigation: Final state:', $menuItem.hasClass('open') ? 'open' : 'closed');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensure hamburger menu works
|
// Ensure hamburger menu works
|
||||||
|
|
@ -46,7 +60,14 @@ jQuery(document).ready(function($) {
|
||||||
if ($hamburger.length && $menu.length) {
|
if ($hamburger.length && $menu.length) {
|
||||||
console.log('HVAC Navigation: Setting up hamburger menu');
|
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.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
|
@ -61,6 +82,26 @@ jQuery(document).ready(function($) {
|
||||||
|
|
||||||
console.log('HVAC Navigation: Menu is now', isOpen ? 'active' : 'inactive');
|
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)
|
// Close menu when clicking outside (on mobile)
|
||||||
|
|
|
||||||
|
|
@ -507,11 +507,8 @@ class HVAC_Community_Events {
|
||||||
// Community login shortcode - initialize Login_Handler to register the shortcode
|
// Community login shortcode - initialize Login_Handler to register the shortcode
|
||||||
new \HVAC_Community_Events\Community\Login_Handler();
|
new \HVAC_Community_Events\Community\Login_Handler();
|
||||||
|
|
||||||
// Dashboard shortcode
|
// Dashboard shortcodes moved to class-hvac-shortcodes.php for centralization
|
||||||
add_shortcode('hvac_dashboard', array($this, 'render_dashboard'));
|
// The shortcodes are now registered in the centralized shortcode manager
|
||||||
|
|
||||||
// Master Dashboard shortcode
|
|
||||||
add_shortcode('hvac_master_dashboard', array($this, 'render_master_dashboard'));
|
|
||||||
|
|
||||||
// Add the event summary shortcode
|
// Add the event summary shortcode
|
||||||
add_shortcode('hvac_event_summary', array($this, 'render_event_summary'));
|
add_shortcode('hvac_event_summary', array($this, 'render_event_summary'));
|
||||||
|
|
|
||||||
|
|
@ -46,17 +46,37 @@ class HVAC_Dashboard_Data {
|
||||||
public function get_total_events_count() {
|
public function get_total_events_count() {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
// Use direct database query to avoid TEC query hijacking
|
try {
|
||||||
$count = $wpdb->get_var( $wpdb->prepare(
|
// Cache key based on user ID
|
||||||
"SELECT COUNT(*) FROM {$wpdb->posts}
|
$cache_key = 'hvac_dashboard_total_events_' . $this->user_id;
|
||||||
WHERE post_type = %s
|
$count = wp_cache_get( $cache_key, 'hvac_dashboard' );
|
||||||
AND post_author = %d
|
|
||||||
AND post_status IN ('publish', 'future', 'draft', 'pending', 'private')",
|
|
||||||
Tribe__Events__Main::POSTTYPE,
|
|
||||||
$this->user_id
|
|
||||||
) );
|
|
||||||
|
|
||||||
return (int) $count;
|
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() {
|
public function get_upcoming_events_count() {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
$today = date( 'Y-m-d H:i:s' );
|
|
||||||
|
|
||||||
// Use direct database query to avoid TEC query hijacking
|
// Cache key based on user ID
|
||||||
$count = $wpdb->get_var( $wpdb->prepare(
|
$cache_key = 'hvac_dashboard_upcoming_events_' . $this->user_id;
|
||||||
"SELECT COUNT(*) FROM {$wpdb->posts} p
|
$count = wp_cache_get( $cache_key, 'hvac_dashboard' );
|
||||||
LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_EventStartDate'
|
|
||||||
WHERE p.post_type = %s
|
if ( false === $count ) {
|
||||||
AND p.post_author = %d
|
$today = date( 'Y-m-d H:i:s' );
|
||||||
AND p.post_status IN ('publish', 'future')
|
|
||||||
AND (pm.meta_value >= %s OR pm.meta_value IS NULL)",
|
// Use direct database query to avoid TEC query hijacking
|
||||||
Tribe__Events__Main::POSTTYPE,
|
$count = $wpdb->get_var( $wpdb->prepare(
|
||||||
$this->user_id,
|
"SELECT COUNT(*) FROM {$wpdb->posts} p
|
||||||
$today
|
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;
|
return (int) $count;
|
||||||
}
|
}
|
||||||
|
|
@ -91,20 +121,30 @@ class HVAC_Dashboard_Data {
|
||||||
*/
|
*/
|
||||||
public function get_past_events_count() {
|
public function get_past_events_count() {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
$today = date( 'Y-m-d H:i:s' );
|
|
||||||
|
|
||||||
// Use direct database query to avoid TEC query hijacking
|
// Cache key based on user ID
|
||||||
$count = $wpdb->get_var( $wpdb->prepare(
|
$cache_key = 'hvac_dashboard_past_events_' . $this->user_id;
|
||||||
"SELECT COUNT(*) FROM {$wpdb->posts} p
|
$count = wp_cache_get( $cache_key, 'hvac_dashboard' );
|
||||||
LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_EventEndDate'
|
|
||||||
WHERE p.post_type = %s
|
if ( false === $count ) {
|
||||||
AND p.post_author = %d
|
$today = date( 'Y-m-d H:i:s' );
|
||||||
AND p.post_status IN ('publish', 'private')
|
|
||||||
AND pm.meta_value < %s",
|
// Use direct database query to avoid TEC query hijacking
|
||||||
Tribe__Events__Main::POSTTYPE,
|
$count = $wpdb->get_var( $wpdb->prepare(
|
||||||
$this->user_id,
|
"SELECT COUNT(*) FROM {$wpdb->posts} p
|
||||||
$today
|
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;
|
return (int) $count;
|
||||||
}
|
}
|
||||||
|
|
@ -117,41 +157,51 @@ class HVAC_Dashboard_Data {
|
||||||
public function get_total_tickets_sold() {
|
public function get_total_tickets_sold() {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
// Count TEC Commerce attendees
|
// Cache key based on user ID
|
||||||
$tec_commerce_count = $wpdb->get_var( $wpdb->prepare(
|
$cache_key = 'hvac_dashboard_tickets_sold_' . $this->user_id;
|
||||||
"SELECT COUNT(*) FROM {$wpdb->posts} p
|
$total = wp_cache_get( $cache_key, 'hvac_dashboard' );
|
||||||
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
|
if ( false === $total ) {
|
||||||
$tribe_tpp_count = $wpdb->get_var( $wpdb->prepare(
|
// Count TEC Commerce attendees
|
||||||
"SELECT COUNT(*) FROM {$wpdb->posts} p
|
$tec_commerce_count = $wpdb->get_var( $wpdb->prepare(
|
||||||
INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_tribe_tpp_event'
|
"SELECT COUNT(*) FROM {$wpdb->posts} p
|
||||||
WHERE p.post_type = %s
|
INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_tec_tickets_commerce_event'
|
||||||
AND pm.meta_value IN (
|
WHERE p.post_type = %s
|
||||||
SELECT ID FROM {$wpdb->posts}
|
AND pm.meta_value IN (
|
||||||
WHERE post_type = %s
|
SELECT ID FROM {$wpdb->posts}
|
||||||
AND post_author = %d
|
WHERE post_type = %s
|
||||||
AND post_status IN ('publish', 'private')
|
AND post_author = %d
|
||||||
)",
|
AND post_status IN ('publish', 'private')
|
||||||
'tribe_tpp_attendees',
|
)",
|
||||||
'tribe_events',
|
'tec_tc_attendee',
|
||||||
$this->user_id
|
'tribe_events',
|
||||||
) );
|
$this->user_id
|
||||||
|
) );
|
||||||
|
|
||||||
// Note: RSVP attendees are not counted as "tickets sold" since they are free registrations
|
// 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
|
||||||
|
) );
|
||||||
|
|
||||||
return (int) ($tec_commerce_count + $tribe_tpp_count);
|
// 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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $total;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -162,8 +212,13 @@ class HVAC_Dashboard_Data {
|
||||||
public function get_total_revenue() {
|
public function get_total_revenue() {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
// Get TEC Commerce revenue
|
// Cache key based on user ID
|
||||||
$tec_commerce_revenue = $wpdb->get_var( $wpdb->prepare(
|
$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)))
|
"SELECT SUM(CAST(pm_price.meta_value AS DECIMAL(10,2)))
|
||||||
FROM {$wpdb->posts} p
|
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'
|
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
|
$this->user_id
|
||||||
) );
|
) );
|
||||||
|
|
||||||
// Note: RSVP attendees typically don't have revenue (free tickets)
|
// Note: RSVP attendees typically don't have revenue (free tickets)
|
||||||
|
|
||||||
return (float) (($tec_commerce_revenue ?: 0.00) + ($tribe_tpp_revenue ?: 0.00));
|
$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 $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
|
* Count the number of attendees for an event by querying attendee posts
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -73,8 +73,10 @@ class HVAC_Dashboard {
|
||||||
private function get_dashboard_content() {
|
private function get_dashboard_content() {
|
||||||
$user_id = get_current_user_id();
|
$user_id = get_current_user_id();
|
||||||
|
|
||||||
// Include dashboard data class
|
// Get dashboard data instance (class is autoloaded)
|
||||||
require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-dashboard-data.php';
|
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);
|
$dashboard_data = new HVAC_Dashboard_Data($user_id);
|
||||||
|
|
||||||
// Get data
|
// Get data
|
||||||
|
|
@ -197,8 +199,8 @@ class HVAC_Dashboard {
|
||||||
<td>$<?php echo esc_html(number_format($event['revenue'], 2)); ?></td>
|
<td>$<?php echo esc_html(number_format($event['revenue'], 2)); ?></td>
|
||||||
<td>
|
<td>
|
||||||
<?php
|
<?php
|
||||||
$edit_url = add_query_arg('event_id', $event['id'], home_url('/manage-event/'));
|
$edit_url = add_query_arg('event_id', $event['id'], home_url('/trainer/event/edit/'));
|
||||||
$summary_url = get_permalink($event['id']);
|
$summary_url = add_query_arg('event_id', $event['id'], home_url('/trainer/event/summary/'));
|
||||||
?>
|
?>
|
||||||
<a href="<?php echo esc_url($edit_url); ?>">Edit</a> |
|
<a href="<?php echo esc_url($edit_url); ?>">Edit</a> |
|
||||||
<a href="<?php echo esc_url($summary_url); ?>">Summary</a>
|
<a href="<?php echo esc_url($summary_url); ?>">Summary</a>
|
||||||
|
|
@ -221,18 +223,31 @@ class HVAC_Dashboard {
|
||||||
* Handle AJAX request for filtered events table
|
* Handle AJAX request for filtered events table
|
||||||
*/
|
*/
|
||||||
public function ajax_filter_events() {
|
public function ajax_filter_events() {
|
||||||
// Check nonce
|
try {
|
||||||
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_dashboard_nonce')) {
|
// Check nonce
|
||||||
wp_send_json_error(array('message' => 'Security check failed.'));
|
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_dashboard_nonce')) {
|
||||||
return;
|
$this->log_error('Dashboard AJAX: Nonce verification failed', $_POST);
|
||||||
}
|
wp_send_json_error(array('message' => 'Security check failed.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Get current user ID
|
// Get current user ID
|
||||||
$user_id = get_current_user_id();
|
$user_id = get_current_user_id();
|
||||||
if (!$user_id || !current_user_can('view_hvac_dashboard')) {
|
if (!$user_id) {
|
||||||
wp_send_json_error(array('message' => 'Access denied.'));
|
$this->log_error('Dashboard AJAX: User not logged in');
|
||||||
return;
|
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
|
// Get all filters and parameters
|
||||||
$args = array(
|
$args = array(
|
||||||
|
|
@ -246,8 +261,10 @@ class HVAC_Dashboard {
|
||||||
'date_to' => isset($_POST['date_to']) ? sanitize_text_field($_POST['date_to']) : '',
|
'date_to' => isset($_POST['date_to']) ? sanitize_text_field($_POST['date_to']) : '',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Include dashboard data class
|
// Get dashboard data instance (class is autoloaded)
|
||||||
require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-dashboard-data.php';
|
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);
|
$dashboard_data = new HVAC_Dashboard_Data($user_id);
|
||||||
|
|
||||||
// Get filtered events data
|
// Get filtered events data
|
||||||
|
|
@ -412,6 +429,30 @@ class HVAC_Dashboard {
|
||||||
'pagination' => $pagination,
|
'pagination' => $pagination,
|
||||||
'args' => $args
|
'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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,11 @@ class HVAC_Menu_System {
|
||||||
add_action('init', array($this, 'register_menu_locations'));
|
add_action('init', array($this, 'register_menu_locations'));
|
||||||
add_action('wp', array($this, 'setup_trainer_menu'));
|
add_action('wp', array($this, 'setup_trainer_menu'));
|
||||||
add_action('wp_enqueue_scripts', array($this, 'enqueue_menu_styles'));
|
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);
|
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();
|
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
|
* Get menu structure based on user capabilities
|
||||||
*/
|
*/
|
||||||
|
|
@ -337,21 +349,63 @@ class HVAC_Menu_System {
|
||||||
* Enqueue menu styles and scripts
|
* Enqueue menu styles and scripts
|
||||||
*/
|
*/
|
||||||
public function enqueue_menu_styles() {
|
public function enqueue_menu_styles() {
|
||||||
if ($this->is_trainer_page() && $this->user_can_access_trainer_area()) {
|
// AGGRESSIVE: Enqueue on ALL pages to prevent missing JavaScript issues
|
||||||
wp_enqueue_style(
|
// The menu is only rendered when user has proper access, so this is safe
|
||||||
'hvac-menu-system',
|
|
||||||
HVAC_PLUGIN_URL . 'assets/css/hvac-menu-system.css',
|
|
||||||
array(),
|
|
||||||
HVAC_PLUGIN_VERSION
|
|
||||||
);
|
|
||||||
|
|
||||||
wp_enqueue_script(
|
wp_enqueue_style(
|
||||||
'hvac-menu-system',
|
'hvac-menu-system',
|
||||||
$this->get_compatible_script_path('hvac-menu-system'),
|
HVAC_PLUGIN_URL . 'assets/css/hvac-menu-system.css',
|
||||||
array('jquery'),
|
array(),
|
||||||
HVAC_PLUGIN_VERSION,
|
HVAC_PLUGIN_VERSION
|
||||||
true
|
);
|
||||||
);
|
|
||||||
|
// 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')) {
|
||||||
|
?>
|
||||||
|
<link rel="stylesheet" id="hvac-menu-system-direct" href="<?php echo esc_url(HVAC_PLUGIN_URL . 'assets/css/hvac-menu-system.css?ver=' . HVAC_PLUGIN_VERSION); ?>" />
|
||||||
|
<script>
|
||||||
|
// Wait for jQuery to be available before loading navigation script
|
||||||
|
(function checkJQuery() {
|
||||||
|
if (typeof jQuery !== 'undefined') {
|
||||||
|
var script = document.createElement('script');
|
||||||
|
script.id = 'hvac-navigation-robust-direct';
|
||||||
|
script.src = '<?php echo esc_url(HVAC_PLUGIN_URL . 'assets/js/hvac-navigation-robust.js?ver=' . HVAC_PLUGIN_VERSION); ?>';
|
||||||
|
document.head.appendChild(script);
|
||||||
|
} else {
|
||||||
|
setTimeout(checkJQuery, 50);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -294,18 +294,14 @@ class HVAC_Scripts_Styles {
|
||||||
$this->version
|
$this->version
|
||||||
);
|
);
|
||||||
|
|
||||||
// Load simple navigation styles with high priority
|
// Navigation styles now handled by consolidated core CSS
|
||||||
wp_enqueue_style(
|
// Removed hvac-navigation-simple.css to prevent CSS conflicts
|
||||||
'hvac-navigation-simple',
|
// The consolidated core CSS has proper flex-direction: row declarations
|
||||||
HVAC_PLUGIN_URL . 'assets/css/hvac-navigation-simple.css',
|
|
||||||
array('hvac-design-system', 'hvac-components'),
|
|
||||||
$this->version
|
|
||||||
);
|
|
||||||
|
|
||||||
// Always load fixed core bundle
|
// Always load full core bundle (contains navigation CSS)
|
||||||
wp_enqueue_style(
|
wp_enqueue_style(
|
||||||
'hvac-consolidated-core',
|
'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'),
|
array('hvac-design-system', 'hvac-components'),
|
||||||
$this->version
|
$this->version
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,10 @@ class HVAC_Welcome_Popup {
|
||||||
* Enqueue popup assets on dashboard page
|
* Enqueue popup assets on dashboard page
|
||||||
*/
|
*/
|
||||||
public function enqueue_assets() {
|
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
|
// Only load on trainer dashboard page
|
||||||
if (!$this->is_dashboard_page()) {
|
if (!$this->is_dashboard_page()) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ define('HVAC_IN_PAGE_TEMPLATE', true);
|
||||||
get_header();
|
get_header();
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<!-- DEBUG: page-trainer-dashboard.php REFACTORED template loaded -->
|
|
||||||
<div class="hvac-page-wrapper hvac-trainer-dashboard-page">
|
<div class="hvac-page-wrapper hvac-trainer-dashboard-page">
|
||||||
<?php
|
<?php
|
||||||
// Display trainer navigation menu
|
// Display trainer navigation menu
|
||||||
|
|
@ -32,48 +31,18 @@ get_header();
|
||||||
// Ensure user is logged in and has access to the dashboard
|
// Ensure user is logged in and has access to the dashboard
|
||||||
if ( ! is_user_logged_in() ) {
|
if ( ! is_user_logged_in() ) {
|
||||||
// Redirect to login page if not logged in
|
// Redirect to login page if not logged in
|
||||||
wp_safe_redirect( home_url( '/community-login/' ) );
|
wp_safe_redirect( home_url( '/training-login/' ) );
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user has permission to view dashboard
|
// Check if user has permission to view dashboard
|
||||||
// Allow administrators and users with view_hvac_dashboard capability
|
// Check for HVAC trainer roles (not capabilities!)
|
||||||
if ( ! current_user_can( 'view_hvac_dashboard' ) && ! current_user_can( 'manage_options' ) ) {
|
$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' ) ) {
|
||||||
// Show access denied message instead of redirect to prevent loops
|
// Show access denied message instead of redirect to prevent loops
|
||||||
?>
|
?>
|
||||||
<style>
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div class="hvac-access-denied">
|
<div class="hvac-access-denied">
|
||||||
<h1><?php _e('Access Denied', 'hvac-community-events'); ?></h1>
|
<h1><?php _e('Access Denied', 'hvac-community-events'); ?></h1>
|
||||||
<p><?php _e('Sorry, you do not have permission to access the HVAC Trainer Dashboard.', 'hvac-community-events'); ?></p>
|
<p><?php _e('Sorry, you do not have permission to access the HVAC Trainer Dashboard.', 'hvac-community-events'); ?></p>
|
||||||
|
|
@ -87,8 +56,10 @@ get_header();
|
||||||
// Get the current user ID
|
// Get the current user ID
|
||||||
$user_id = get_current_user_id();
|
$user_id = get_current_user_id();
|
||||||
|
|
||||||
// Include and instantiate the dashboard data class
|
// Get dashboard data instance (class is autoloaded)
|
||||||
require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-dashboard-data.php';
|
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 );
|
$dashboard_data = new HVAC_Dashboard_Data( $user_id );
|
||||||
|
|
||||||
// Fetch data
|
// Fetch data
|
||||||
|
|
@ -369,71 +340,6 @@ get_header();
|
||||||
|
|
||||||
</div> <!-- .hvac-dashboard-wrapper -->
|
</div> <!-- .hvac-dashboard-wrapper -->
|
||||||
|
|
||||||
<style>
|
|
||||||
/* Dashboard Stats Flexbox Layout */
|
|
||||||
.hvac-stats-row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin: -10px;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hvac-stat-col {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 160px;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hvac-stat-card {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hvac-stat-card h3 {
|
|
||||||
margin: 0 0 10px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: normal;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hvac-stat-card .metric-value {
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #E9AF28;
|
|
||||||
margin: 0;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hvac-event-filters {
|
|
||||||
margin: 15px 0 25px 0;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,14 @@ get_header();
|
||||||
HVAC_Menu_System::instance()->render_trainer_menu();
|
HVAC_Menu_System::instance()->render_trainer_menu();
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Display breadcrumbs
|
||||||
|
if (class_exists('HVAC_Breadcrumbs')) {
|
||||||
|
echo HVAC_Breadcrumbs::instance()->render_breadcrumbs();
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<?php
|
<?php
|
||||||
// Render the organizers list shortcode
|
// Render the organizers list shortcode
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,14 @@ get_header();
|
||||||
HVAC_Menu_System::instance()->render_trainer_menu();
|
HVAC_Menu_System::instance()->render_trainer_menu();
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Display breadcrumbs
|
||||||
|
if (class_exists('HVAC_Breadcrumbs')) {
|
||||||
|
echo HVAC_Breadcrumbs::instance()->render_breadcrumbs();
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<?php
|
<?php
|
||||||
// Render the venues list shortcode
|
// Render the venues list shortcode
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue