Major fixes implemented: 1. CSS Loading on Hierarchical Pages - FIXED - Enhanced page detection logic in hvac-community-events.php - Added URL pattern matching for /trainer/* and /master-trainer/* - All 7 HVAC CSS files now load correctly on hierarchical pages 2. Google Sheets Infinite Redirect Loop - FIXED - Removed duplicate master-trainer-google-sheets page - Added redirect loop prevention with hvac_redirect_check parameter - Disabled WordPress canonical redirects for Google Sheets URLs - Page now loads in 2.4s with 0 redirects (was 50+ before) 3. Google Sheets Folder Manager Integration - Moved folder manager to proper location in includes/google-sheets/ - Added conditional file loading to prevent fatal errors - Enhanced error handling throughout Google Sheets components 4. Dashboard Navigation Improvements - Fixed duplicate navigation buttons - Enhanced Master Trainer dashboard with folder hierarchy support - Improved permission checks and role-based access Technical improvements: - Added comprehensive debugging capabilities - Enhanced error handling with try-catch blocks - Improved conditional file loading patterns - Fixed hardcoded URLs in Google Sheets admin All issues tested and verified working on staging environment. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
483 lines
No EOL
16 KiB
PHP
483 lines
No EOL
16 KiB
PHP
<?php
|
|
/**
|
|
* Template Name: HVAC Master Trainer Dashboard
|
|
*
|
|
* This template handles the display of the HVAC Master Trainer Dashboard.
|
|
* It shows aggregate data across ALL trainers and events.
|
|
*
|
|
* @package HVAC Community Events
|
|
* @subpackage Templates
|
|
* @author Ben Reed
|
|
* @version 1.0.0
|
|
*/
|
|
|
|
// Exit if accessed directly.
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
// --- Security Check & Data Loading ---
|
|
|
|
// Ensure user is logged in and has access to the master dashboard
|
|
if ( ! is_user_logged_in() ) {
|
|
// Redirect to login page if not logged in
|
|
wp_safe_redirect( home_url( '/training-login/' ) );
|
|
exit;
|
|
}
|
|
|
|
// Check if user has permission to view master dashboard
|
|
if ( ! current_user_can( 'view_master_dashboard' ) && ! current_user_can( 'view_all_trainer_data' ) && ! current_user_can( 'manage_options' ) ) {
|
|
// Show access denied message using existing styles
|
|
get_header();
|
|
?>
|
|
<div id="primary" class="content-area primary ast-container">
|
|
<main id="main" class="site-main">
|
|
<div class="hvac-dashboard-header">
|
|
<h1 class="entry-title">Access Denied</h1>
|
|
</div>
|
|
<div class="hvac-dashboard-stats">
|
|
<div class="hvac-stats-row">
|
|
<div class="hvac-stat-col">
|
|
<div class="hvac-stat-card" style="text-align: center; padding: 40px;">
|
|
<p style="color: #d63638; font-size: 18px; margin-bottom: 20px;">You do not have permission to view the Master Dashboard.</p>
|
|
<p style="margin-bottom: 20px;">This dashboard is only available to Master Trainers and Administrators.</p>
|
|
<a href="<?php echo home_url( '/trainer/dashboard/' ); ?>" class="ast-button ast-button-primary">Go to Your Dashboard</a>
|
|
<a href="<?php echo home_url(); ?>" class="ast-button ast-button-secondary">Return to Home</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
<?php
|
|
get_footer();
|
|
exit;
|
|
}
|
|
|
|
// Get current user info
|
|
$current_user = wp_get_current_user();
|
|
$user_id = $current_user->ID;
|
|
|
|
// Load master dashboard data class
|
|
if ( ! class_exists( 'HVAC_Master_Dashboard_Data' ) ) {
|
|
require_once HVAC_CE_PLUGIN_DIR . 'includes/class-hvac-master-dashboard-data.php';
|
|
}
|
|
|
|
// Initialize master dashboard data handler (no user ID needed - shows all data)
|
|
$master_data = new HVAC_Master_Dashboard_Data();
|
|
|
|
// Handle AJAX request for events table
|
|
if ( defined('DOING_AJAX') && DOING_AJAX && isset($_POST['action']) && $_POST['action'] === 'hvac_master_dashboard_events' ) {
|
|
// Verify nonce
|
|
if ( ! wp_verify_nonce( $_POST['nonce'], 'hvac_master_dashboard_nonce' ) ) {
|
|
wp_die( 'Security check failed' );
|
|
}
|
|
|
|
// Get table data with filters
|
|
$args = array(
|
|
'status' => sanitize_text_field( $_POST['status'] ?? 'all' ),
|
|
'search' => sanitize_text_field( $_POST['search'] ?? '' ),
|
|
'orderby' => sanitize_text_field( $_POST['orderby'] ?? 'date' ),
|
|
'order' => sanitize_text_field( $_POST['order'] ?? 'DESC' ),
|
|
'page' => absint( $_POST['page'] ?? 1 ),
|
|
'per_page' => absint( $_POST['per_page'] ?? 10 ),
|
|
'date_from' => sanitize_text_field( $_POST['date_from'] ?? '' ),
|
|
'date_to' => sanitize_text_field( $_POST['date_to'] ?? '' ),
|
|
'trainer_id' => absint( $_POST['trainer_id'] ?? 0 ),
|
|
);
|
|
|
|
$table_data = $master_data->get_events_table_data( $args );
|
|
wp_send_json_success( $table_data );
|
|
}
|
|
|
|
// Get statistics
|
|
$total_events = $master_data->get_total_events_count();
|
|
$upcoming_events = $master_data->get_upcoming_events_count();
|
|
$past_events = $master_data->get_past_events_count();
|
|
$total_tickets_sold = $master_data->get_total_tickets_sold();
|
|
$total_revenue = $master_data->get_total_revenue();
|
|
$trainer_stats = $master_data->get_trainer_statistics();
|
|
|
|
// Get events table data (default view)
|
|
$default_args = array(
|
|
'status' => 'all',
|
|
'orderby' => 'date',
|
|
'order' => 'DESC',
|
|
'page' => 1,
|
|
'per_page' => 10
|
|
);
|
|
$events_table_data = $master_data->get_events_table_data( $default_args );
|
|
|
|
// Get list of all trainers for filter dropdown
|
|
$all_trainers = get_users(array(
|
|
'role__in' => array('hvac_trainer', 'hvac_master_trainer'),
|
|
'fields' => array('ID', 'display_name')
|
|
));
|
|
|
|
// Error handling for access denied
|
|
$error_message = '';
|
|
if ( isset( $_GET['error'] ) && $_GET['error'] === 'access_denied' ) {
|
|
$error_message = 'You were redirected here because you do not have permission to access the Master Dashboard.';
|
|
}
|
|
|
|
?>
|
|
|
|
<div id="primary" class="content-area primary ast-container">
|
|
<main id="main" class="site-main">
|
|
|
|
<?php if ( $error_message ): ?>
|
|
<div class="hvac-dashboard-header">
|
|
<div class="hvac-stat-card" style="background: #fff3cd; border-left: 4px solid #856404; padding: 15px; margin-bottom: 20px;">
|
|
<p style="color: #856404; margin: 0;"><?php echo esc_html( $error_message ); ?></p>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Dashboard Header & Navigation -->
|
|
<div class="hvac-dashboard-header">
|
|
<h1 class="entry-title">Master Dashboard</h1>
|
|
<div class="hvac-dashboard-nav">
|
|
<?php if (current_user_can('manage_google_sheets_integration')): ?>
|
|
<a href="<?php echo home_url('/master-trainer/google-sheets/'); ?>" class="ast-button ast-button-primary">Google Sheets</a>
|
|
<?php endif; ?>
|
|
<?php if (current_user_can('manage_communication_templates')): ?>
|
|
<a href="<?php echo home_url('/trainer/communication-templates/'); ?>" class="ast-button ast-button-primary">Templates</a>
|
|
<?php endif; ?>
|
|
<a href="<?php echo home_url('/trainer/dashboard/'); ?>" class="ast-button ast-button-secondary">Trainer Dashboard</a>
|
|
<a href="<?php echo esc_url( wp_logout_url( home_url( '/training-login/' ) ) ); ?>" class="ast-button ast-button-secondary">Logout</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- System Overview Statistics -->
|
|
<section class="hvac-dashboard-stats">
|
|
<h2>System Overview</h2>
|
|
<div class="hvac-stats-row">
|
|
|
|
<!-- Stat Card: Total Events -->
|
|
<div class="hvac-stat-col">
|
|
<div class="hvac-stat-card">
|
|
<h3>Total Events</h3>
|
|
<p><?php echo number_format( $total_events ); ?></p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stat Card: Upcoming Events -->
|
|
<div class="hvac-stat-col">
|
|
<div class="hvac-stat-card">
|
|
<h3>Upcoming Events</h3>
|
|
<p><?php echo number_format( $upcoming_events ); ?></p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stat Card: Completed Events -->
|
|
<div class="hvac-stat-col">
|
|
<div class="hvac-stat-card">
|
|
<h3>Completed Events</h3>
|
|
<p><?php echo number_format( $past_events ); ?></p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stat Card: Active Trainers -->
|
|
<div class="hvac-stat-col">
|
|
<div class="hvac-stat-card">
|
|
<h3>Active Trainers</h3>
|
|
<p><?php echo number_format( $trainer_stats['total_trainers'] ); ?></p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stat Card: Tickets Sold -->
|
|
<div class="hvac-stat-col">
|
|
<div class="hvac-stat-card">
|
|
<h3>Tickets Sold</h3>
|
|
<p><?php echo number_format( $total_tickets_sold ); ?></p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stat Card: Total Revenue -->
|
|
<div class="hvac-stat-col">
|
|
<div class="hvac-stat-card">
|
|
<h3>Total Revenue</h3>
|
|
<p>$<?php echo number_format( $total_revenue, 2 ); ?></p>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Trainer Analytics Section -->
|
|
<section id="trainers" class="dashboard-section">
|
|
<h2 class="section-title">Trainer Performance Analytics</h2>
|
|
|
|
<?php if ( ! empty( $trainer_stats['trainer_data'] ) ): ?>
|
|
<div class="trainers-table-container">
|
|
<table class="trainers-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Trainer Name</th>
|
|
<th>Email</th>
|
|
<th>Total Events</th>
|
|
<th>Upcoming</th>
|
|
<th>Completed</th>
|
|
<th>Attendees</th>
|
|
<th>Revenue</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ( $trainer_stats['trainer_data'] as $trainer ): ?>
|
|
<tr>
|
|
<td class="trainer-name">
|
|
<strong><?php echo esc_html( $trainer->display_name ); ?></strong>
|
|
</td>
|
|
<td><?php echo esc_html( $trainer->user_email ); ?></td>
|
|
<td class="number"><?php echo number_format( $trainer->total_events ); ?></td>
|
|
<td class="number"><?php echo number_format( $trainer->upcoming_events ); ?></td>
|
|
<td class="number"><?php echo number_format( $trainer->past_events ); ?></td>
|
|
<td class="number"><?php echo number_format( $trainer->total_attendees ); ?></td>
|
|
<td class="revenue">$<?php echo number_format( $trainer->total_revenue, 2 ); ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="no-data-message">
|
|
<p>No trainer data available.</p>
|
|
</div>
|
|
<?php endif; ?>
|
|
</section>
|
|
|
|
<!-- All Events Section -->
|
|
<section id="events" class="dashboard-section">
|
|
<h2 class="section-title">All Events Management</h2>
|
|
|
|
<!-- Events Table Filters -->
|
|
<div class="events-filters">
|
|
<div class="filter-group">
|
|
<label for="events-status-filter">Status:</label>
|
|
<select id="events-status-filter" name="status">
|
|
<option value="all">All Events</option>
|
|
<option value="publish">Published</option>
|
|
<option value="future">Upcoming</option>
|
|
<option value="draft">Draft</option>
|
|
<option value="pending">Pending</option>
|
|
<option value="private">Private</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="filter-group">
|
|
<label for="events-trainer-filter">Trainer:</label>
|
|
<select id="events-trainer-filter" name="trainer_id">
|
|
<option value="">All Trainers</option>
|
|
<?php foreach ( $all_trainers as $trainer ): ?>
|
|
<option value="<?php echo esc_attr( $trainer->ID ); ?>">
|
|
<?php echo esc_html( $trainer->display_name ); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="filter-group">
|
|
<label for="events-search">Search:</label>
|
|
<input type="text" id="events-search" name="search" placeholder="Event name...">
|
|
</div>
|
|
|
|
<div class="filter-group">
|
|
<label for="events-date-from">From:</label>
|
|
<input type="date" id="events-date-from" name="date_from">
|
|
</div>
|
|
|
|
<div class="filter-group">
|
|
<label for="events-date-to">To:</label>
|
|
<input type="date" id="events-date-to" name="date_to">
|
|
</div>
|
|
|
|
<button type="button" id="apply-events-filters" class="btn btn-primary">Apply Filters</button>
|
|
<button type="button" id="reset-events-filters" class="btn btn-secondary">Reset</button>
|
|
</div>
|
|
|
|
<!-- Events Table -->
|
|
<div id="events-table-container" class="events-table-container">
|
|
<!-- Table will be loaded here via AJAX -->
|
|
<div class="loading-placeholder">Loading events...</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- Master Dashboard JavaScript -->
|
|
<script>
|
|
jQuery(document).ready(function($) {
|
|
var eventsTable = {
|
|
currentPage: 1,
|
|
perPage: 10,
|
|
orderBy: 'date',
|
|
order: 'DESC',
|
|
|
|
init: function() {
|
|
this.bindEvents();
|
|
this.loadEventsTable();
|
|
},
|
|
|
|
bindEvents: function() {
|
|
var self = this;
|
|
|
|
// Filter controls
|
|
$('#apply-events-filters').on('click', function() {
|
|
self.currentPage = 1;
|
|
self.loadEventsTable();
|
|
});
|
|
|
|
$('#reset-events-filters').on('click', function() {
|
|
$('#events-status-filter').val('all');
|
|
$('#events-trainer-filter').val('');
|
|
$('#events-search').val('');
|
|
$('#events-date-from').val('');
|
|
$('#events-date-to').val('');
|
|
self.currentPage = 1;
|
|
self.loadEventsTable();
|
|
});
|
|
|
|
// Pagination will be bound dynamically when table loads
|
|
},
|
|
|
|
loadEventsTable: function() {
|
|
var self = this;
|
|
var container = $('#events-table-container');
|
|
|
|
container.html('<div class="loading-placeholder">Loading events...</div>');
|
|
|
|
var data = {
|
|
action: 'hvac_master_dashboard_events',
|
|
nonce: '<?php echo wp_create_nonce("hvac_master_dashboard_nonce"); ?>',
|
|
status: $('#events-status-filter').val(),
|
|
trainer_id: $('#events-trainer-filter').val(),
|
|
search: $('#events-search').val(),
|
|
date_from: $('#events-date-from').val(),
|
|
date_to: $('#events-date-to').val(),
|
|
page: self.currentPage,
|
|
per_page: self.perPage,
|
|
orderby: self.orderBy,
|
|
order: self.order
|
|
};
|
|
|
|
$.post(ajaxurl, data, function(response) {
|
|
if (response.success) {
|
|
self.renderEventsTable(response.data);
|
|
} else {
|
|
container.html('<div class="error-message">Error loading events table.</div>');
|
|
}
|
|
});
|
|
},
|
|
|
|
renderEventsTable: function(data) {
|
|
var container = $('#events-table-container');
|
|
var html = '';
|
|
|
|
if (data.events && data.events.length > 0) {
|
|
html += '<table class="events-table">';
|
|
html += '<thead><tr>';
|
|
html += '<th>Event Name</th>';
|
|
html += '<th>Trainer</th>';
|
|
html += '<th>Status</th>';
|
|
html += '<th>Date</th>';
|
|
html += '<th>Attendees</th>';
|
|
html += '<th>Revenue</th>';
|
|
html += '<th>Actions</th>';
|
|
html += '</tr></thead>';
|
|
html += '<tbody>';
|
|
|
|
data.events.forEach(function(event) {
|
|
html += '<tr>';
|
|
html += '<td><a href="' + event.link + '" target="_blank">' + event.name + '</a></td>';
|
|
html += '<td>' + event.trainer_name + '</td>';
|
|
html += '<td><span class="status-badge status-' + event.status + '">' + event.status + '</span></td>';
|
|
html += '<td>' + new Date(event.start_date_ts * 1000).toLocaleDateString() + '</td>';
|
|
html += '<td class="number">' + event.sold + ' / ' + event.capacity + '</td>';
|
|
html += '<td class="revenue">$' + parseFloat(event.revenue).toFixed(2) + '</td>';
|
|
html += '<td><a href="' + event.link + '" class="btn btn-small" target="_blank">View</a></td>';
|
|
html += '</tr>';
|
|
});
|
|
|
|
html += '</tbody></table>';
|
|
|
|
// Add pagination
|
|
if (data.pagination.total_pages > 1) {
|
|
html += this.renderPagination(data.pagination);
|
|
}
|
|
} else {
|
|
html = '<div class="no-data-message"><p>No events found matching your criteria.</p></div>';
|
|
}
|
|
|
|
container.html(html);
|
|
this.bindPaginationEvents();
|
|
},
|
|
|
|
renderPagination: function(pagination) {
|
|
var html = '<div class="pagination-container">';
|
|
html += '<div class="pagination-info">';
|
|
html += 'Showing page ' + pagination.current_page + ' of ' + pagination.total_pages;
|
|
html += ' (' + pagination.total_items + ' total events)';
|
|
html += '</div>';
|
|
html += '<div class="pagination-controls">';
|
|
|
|
if (pagination.has_prev) {
|
|
html += '<button class="pagination-btn" data-page="' + (pagination.current_page - 1) + '">← Previous</button>';
|
|
}
|
|
|
|
// Show page numbers (simplified - just current page context)
|
|
var startPage = Math.max(1, pagination.current_page - 2);
|
|
var endPage = Math.min(pagination.total_pages, pagination.current_page + 2);
|
|
|
|
for (var i = startPage; i <= endPage; i++) {
|
|
var activeClass = (i === pagination.current_page) ? ' active' : '';
|
|
html += '<button class="pagination-btn page-btn' + activeClass + '" data-page="' + i + '">' + i + '</button>';
|
|
}
|
|
|
|
if (pagination.has_next) {
|
|
html += '<button class="pagination-btn" data-page="' + (pagination.current_page + 1) + '">Next →</button>';
|
|
}
|
|
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
return html;
|
|
},
|
|
|
|
bindPaginationEvents: function() {
|
|
var self = this;
|
|
$('.pagination-btn').on('click', function() {
|
|
var page = parseInt($(this).data('page'));
|
|
if (page && page !== self.currentPage) {
|
|
self.currentPage = page;
|
|
self.loadEventsTable();
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
// Initialize events table
|
|
eventsTable.init();
|
|
|
|
// Navigation smooth scrolling
|
|
$('.nav-link').on('click', function(e) {
|
|
var href = $(this).attr('href');
|
|
if (href.startsWith('#')) {
|
|
e.preventDefault();
|
|
var target = $(href);
|
|
if (target.length) {
|
|
$('html, body').animate({
|
|
scrollTop: target.offset().top - 100
|
|
}, 500);
|
|
}
|
|
|
|
// Update active navigation
|
|
$('.nav-link').removeClass('active');
|
|
$(this).addClass('active');
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<!-- AJAX URL for JavaScript -->
|
|
<script>
|
|
var ajaxurl = '<?php echo admin_url("admin-ajax.php"); ?>';
|
|
</script>
|