Systematic audit and implementation of missing Master Trainer functionality with comprehensive WordPress best practices and security implementation. ## Features Implemented - Master Events Overview (/master-trainer/events/) - KPI dashboard with filtering - Import/Export Data Management (/master-trainer/import-export/) - CSV operations - Communication Templates (/trainer/communication-templates/) - Professional templates - Enhanced Announcements (/master-trainer/announcements/) - Dynamic shortcode integration - Pending Approvals System (/master-trainer/pending-approvals/) - Workflow management ## Navigation & UX Improvements - Removed redundant Events link from top-level navigation menu - Reorganized administrative functions under Tools dropdown - Enhanced navigation clarity and professional appearance - Full responsive design with accessibility compliance ## Architecture & Security - 5 new singleton manager classes following WordPress patterns - Comprehensive role-based access control (hvac_master_trainer) - Complete security implementation (nonces, sanitization, escaping) - Performance optimizations with transient caching and conditional loading - Professional error handling and user feedback systems ## Files Added (16 new files) - 4 manager classes: Import/Export, Events Overview, Pending Approvals, Communication Templates - 4 CSS files with responsive design and accessibility features - 4 JavaScript files with AJAX functionality and error handling - 2 new templates: Import/Export, Pending Approvals - 2 enhanced templates: Events Overview, Communication Templates ## Files Modified (14 files) - Core system integration in Plugin, Page Manager, Scripts/Styles classes - Navigation system cleanup in Master Menu System - Enhanced access control and role management - Template updates for dynamic content integration ## Testing & Deployment - Comprehensive testing with Playwright automation - Successful staging deployment and verification - All 5 missing pages now fully functional - Navigation improvements verified working Resolves master trainer area audit requirements with production-ready implementation. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
580 lines
No EOL
20 KiB
JavaScript
580 lines
No EOL
20 KiB
JavaScript
/**
|
|
* HVAC Master Events Overview JavaScript
|
|
*
|
|
* Handles filtering, view switching, and AJAX interactions for the master events overview page
|
|
*/
|
|
|
|
(function($) {
|
|
'use strict';
|
|
|
|
// Global variables
|
|
var currentView = 'table';
|
|
var currentFilters = {};
|
|
var currentPage = 1;
|
|
var loading = false;
|
|
|
|
// Initialize when document is ready
|
|
$(document).ready(function() {
|
|
initializeEventsOverview();
|
|
});
|
|
|
|
/**
|
|
* Initialize the events overview functionality
|
|
*/
|
|
function initializeEventsOverview() {
|
|
if ($('#hvac-master-events-overview').length === 0) {
|
|
return; // Not on events overview page
|
|
}
|
|
|
|
// Load initial data
|
|
loadKPIs();
|
|
loadEventsData();
|
|
|
|
// Bind event handlers
|
|
bindEventHandlers();
|
|
|
|
// Set default date filters (current month)
|
|
setDefaultDateFilters();
|
|
}
|
|
|
|
/**
|
|
* Bind all event handlers
|
|
*/
|
|
function bindEventHandlers() {
|
|
// Filter form submission
|
|
$('#hvac-events-filters').on('submit', function(e) {
|
|
e.preventDefault();
|
|
currentPage = 1; // Reset to first page
|
|
loadEventsData();
|
|
});
|
|
|
|
// Clear filters button
|
|
$('#clear-filters').on('click', function(e) {
|
|
e.preventDefault();
|
|
clearAllFilters();
|
|
});
|
|
|
|
// View toggle buttons
|
|
$('.hvac-view-btn').on('click', function(e) {
|
|
e.preventDefault();
|
|
var newView = $(this).data('view');
|
|
if (newView !== currentView) {
|
|
switchView(newView);
|
|
}
|
|
});
|
|
|
|
// Real-time search
|
|
$('#filter-search').on('input', debounce(function() {
|
|
currentPage = 1;
|
|
loadEventsData();
|
|
}, 500));
|
|
|
|
// Date filter changes
|
|
$('#filter-date-from, #filter-date-to').on('change', function() {
|
|
currentPage = 1;
|
|
loadEventsData();
|
|
});
|
|
|
|
// Other filter changes
|
|
$('#filter-trainer, #filter-status').on('change', function() {
|
|
currentPage = 1;
|
|
loadEventsData();
|
|
});
|
|
|
|
// Pagination (delegated event handling)
|
|
$(document).on('click', '.hvac-pagination-btn', function(e) {
|
|
e.preventDefault();
|
|
var page = $(this).data('page');
|
|
if (page && page !== currentPage && !loading) {
|
|
currentPage = page;
|
|
loadEventsData();
|
|
}
|
|
});
|
|
|
|
// Table sorting (delegated event handling)
|
|
$(document).on('click', '.hvac-sortable', function(e) {
|
|
e.preventDefault();
|
|
var column = $(this).data('column');
|
|
var currentOrder = $(this).data('order') || 'desc';
|
|
var newOrder = currentOrder === 'asc' ? 'desc' : 'asc';
|
|
|
|
// Update sort indicators
|
|
$('.hvac-sortable').removeClass('hvac-sort-asc hvac-sort-desc');
|
|
$(this).addClass('hvac-sort-' + newOrder).data('order', newOrder);
|
|
|
|
// Reload data with new sort
|
|
currentPage = 1;
|
|
loadEventsData(column, newOrder);
|
|
});
|
|
|
|
// KPI refresh
|
|
$(document).on('click', '#refresh-kpis', function(e) {
|
|
e.preventDefault();
|
|
loadKPIs(true);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Set default date filters to current month
|
|
*/
|
|
function setDefaultDateFilters() {
|
|
var today = new Date();
|
|
var firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
|
|
var lastDay = new Date(today.getFullYear(), today.getMonth() + 1, 0);
|
|
|
|
$('#filter-date-from').val(formatDate(firstDay));
|
|
$('#filter-date-to').val(formatDate(lastDay));
|
|
}
|
|
|
|
/**
|
|
* Format date for input field
|
|
*/
|
|
function formatDate(date) {
|
|
return date.toISOString().split('T')[0];
|
|
}
|
|
|
|
/**
|
|
* Clear all filters
|
|
*/
|
|
function clearAllFilters() {
|
|
$('#hvac-events-filters')[0].reset();
|
|
setDefaultDateFilters();
|
|
currentPage = 1;
|
|
loadEventsData();
|
|
}
|
|
|
|
/**
|
|
* Switch between table and calendar views
|
|
*/
|
|
function switchView(newView) {
|
|
if (loading) return;
|
|
|
|
currentView = newView;
|
|
|
|
// Update button states
|
|
$('.hvac-view-btn').removeClass('hvac-view-btn-active');
|
|
$('[data-view="' + newView + '"]').addClass('hvac-view-btn-active');
|
|
|
|
// Hide all views
|
|
$('.hvac-events-table-view, .hvac-events-calendar-view').hide();
|
|
|
|
// Show selected view
|
|
if (newView === 'table') {
|
|
$('.hvac-events-table-view').show();
|
|
loadEventsData();
|
|
} else if (newView === 'calendar') {
|
|
$('.hvac-events-calendar-view').show();
|
|
loadCalendarData();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load KPI data
|
|
*/
|
|
function loadKPIs(forceRefresh) {
|
|
if (!forceRefresh) {
|
|
$('#hvac-kpi-loading').show();
|
|
$('#hvac-kpi-tiles').hide();
|
|
}
|
|
|
|
$.ajax({
|
|
url: hvac_master_events_ajax.ajax_url,
|
|
type: 'POST',
|
|
data: {
|
|
action: 'hvac_master_events_kpis',
|
|
nonce: hvac_master_events_ajax.nonce
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
renderKPIs(response.data);
|
|
$('#hvac-kpi-loading').hide();
|
|
$('#hvac-kpi-tiles').show();
|
|
} else {
|
|
showError('Failed to load KPI data: ' + (response.data.message || 'Unknown error'));
|
|
}
|
|
},
|
|
error: function(xhr, status, error) {
|
|
showError('Error loading KPI data: ' + error);
|
|
$('#hvac-kpi-loading').hide();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Render KPI tiles
|
|
*/
|
|
function renderKPIs(data) {
|
|
var html = '<div class="hvac-kpi-grid">';
|
|
|
|
// Total Events KPI
|
|
html += '<div class="hvac-kpi-tile">';
|
|
html += '<div class="hvac-kpi-icon"><span class="dashicons dashicons-calendar-alt"></span></div>';
|
|
html += '<div class="hvac-kpi-content">';
|
|
html += '<div class="hvac-kpi-number">' + data.total_events + '</div>';
|
|
html += '<div class="hvac-kpi-label">Total Events</div>';
|
|
html += '</div></div>';
|
|
|
|
// Upcoming Events KPI
|
|
html += '<div class="hvac-kpi-tile">';
|
|
html += '<div class="hvac-kpi-icon"><span class="dashicons dashicons-clock"></span></div>';
|
|
html += '<div class="hvac-kpi-content">';
|
|
html += '<div class="hvac-kpi-number">' + data.upcoming_events + '</div>';
|
|
html += '<div class="hvac-kpi-label">Upcoming Events</div>';
|
|
html += '</div></div>';
|
|
|
|
// Active Trainers KPI
|
|
html += '<div class="hvac-kpi-tile">';
|
|
html += '<div class="hvac-kpi-icon"><span class="dashicons dashicons-groups"></span></div>';
|
|
html += '<div class="hvac-kpi-content">';
|
|
html += '<div class="hvac-kpi-number">' + data.active_trainers + '</div>';
|
|
html += '<div class="hvac-kpi-label">Active Trainers</div>';
|
|
html += '</div></div>';
|
|
|
|
// Total Tickets KPI
|
|
html += '<div class="hvac-kpi-tile">';
|
|
html += '<div class="hvac-kpi-icon"><span class="dashicons dashicons-tickets-alt"></span></div>';
|
|
html += '<div class="hvac-kpi-content">';
|
|
html += '<div class="hvac-kpi-number">' + data.total_tickets + '</div>';
|
|
html += '<div class="hvac-kpi-label">Tickets Sold</div>';
|
|
html += '</div></div>';
|
|
|
|
// Total Revenue KPI
|
|
html += '<div class="hvac-kpi-tile">';
|
|
html += '<div class="hvac-kpi-icon"><span class="dashicons dashicons-money-alt"></span></div>';
|
|
html += '<div class="hvac-kpi-content">';
|
|
html += '<div class="hvac-kpi-number">$' + formatMoney(data.total_revenue) + '</div>';
|
|
html += '<div class="hvac-kpi-label">Total Revenue</div>';
|
|
html += '</div></div>';
|
|
|
|
// Past Events KPI
|
|
html += '<div class="hvac-kpi-tile">';
|
|
html += '<div class="hvac-kpi-icon"><span class="dashicons dashicons-yes-alt"></span></div>';
|
|
html += '<div class="hvac-kpi-content">';
|
|
html += '<div class="hvac-kpi-number">' + data.past_events + '</div>';
|
|
html += '<div class="hvac-kpi-label">Past Events</div>';
|
|
html += '</div></div>';
|
|
|
|
html += '</div>';
|
|
|
|
// Add refresh button
|
|
html += '<div class="hvac-kpi-actions">';
|
|
html += '<button type="button" id="refresh-kpis" class="hvac-btn hvac-btn-secondary hvac-btn-sm">';
|
|
html += '<span class="dashicons dashicons-update"></span> Refresh';
|
|
html += '</button></div>';
|
|
|
|
$('#hvac-kpi-tiles').html(html);
|
|
}
|
|
|
|
/**
|
|
* Load events data for table view
|
|
*/
|
|
function loadEventsData(orderby, order) {
|
|
if (loading) return;
|
|
|
|
loading = true;
|
|
$('#hvac-events-loading').show();
|
|
$('#hvac-events-table-container').hide();
|
|
|
|
// Get filter values
|
|
var filterData = getFilterData();
|
|
|
|
// Add sorting parameters
|
|
if (orderby) {
|
|
filterData.orderby = orderby;
|
|
filterData.order = order;
|
|
}
|
|
|
|
$.ajax({
|
|
url: hvac_master_events_ajax.ajax_url,
|
|
type: 'POST',
|
|
data: {
|
|
action: 'hvac_master_events_filter',
|
|
nonce: hvac_master_events_ajax.nonce,
|
|
page: currentPage,
|
|
per_page: 20,
|
|
...filterData
|
|
},
|
|
success: function(response) {
|
|
loading = false;
|
|
|
|
if (response.success) {
|
|
renderEventsTable(response.data.events, response.data.pagination);
|
|
updateEventsCount(response.data.total_found);
|
|
$('#hvac-events-loading').hide();
|
|
$('#hvac-events-table-container').show();
|
|
} else {
|
|
showError('Failed to load events: ' + (response.data.message || 'Unknown error'));
|
|
$('#hvac-events-loading').hide();
|
|
}
|
|
},
|
|
error: function(xhr, status, error) {
|
|
loading = false;
|
|
showError('Error loading events: ' + error);
|
|
$('#hvac-events-loading').hide();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Load calendar data
|
|
*/
|
|
function loadCalendarData() {
|
|
if (loading) return;
|
|
|
|
$('.hvac-calendar-loading').show();
|
|
$('.hvac-calendar-content').hide();
|
|
|
|
var filterData = getFilterData();
|
|
|
|
$.ajax({
|
|
url: hvac_master_events_ajax.ajax_url,
|
|
type: 'POST',
|
|
data: {
|
|
action: 'hvac_master_events_calendar',
|
|
nonce: hvac_master_events_ajax.nonce,
|
|
...filterData
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
renderCalendar(response.data.events);
|
|
updateEventsCount(response.data.total_found);
|
|
$('.hvac-calendar-loading').hide();
|
|
$('.hvac-calendar-content').show();
|
|
} else {
|
|
showError('Failed to load calendar: ' + (response.data.message || 'Unknown error'));
|
|
$('.hvac-calendar-loading').hide();
|
|
}
|
|
},
|
|
error: function(xhr, status, error) {
|
|
showError('Error loading calendar: ' + error);
|
|
$('.hvac-calendar-loading').hide();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get current filter data
|
|
*/
|
|
function getFilterData() {
|
|
return {
|
|
trainer_id: $('#filter-trainer').val() || '',
|
|
date_from: $('#filter-date-from').val() || '',
|
|
date_to: $('#filter-date-to').val() || '',
|
|
status: $('#filter-status').val() || 'all',
|
|
search: $('#filter-search').val() || ''
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Render events table
|
|
*/
|
|
function renderEventsTable(events, pagination) {
|
|
var html = '<div class="hvac-events-table-wrapper">';
|
|
|
|
if (events.length === 0) {
|
|
html += '<div class="hvac-no-events">';
|
|
html += '<p>No events found matching your criteria.</p>';
|
|
html += '<button type="button" id="clear-filters" class="hvac-btn hvac-btn-secondary">Clear Filters</button>';
|
|
html += '</div>';
|
|
} else {
|
|
html += '<table class="hvac-events-table">';
|
|
html += '<thead><tr>';
|
|
html += '<th class="hvac-sortable" data-column="name">Event <span class="hvac-sort-indicator"></span></th>';
|
|
html += '<th class="hvac-sortable" data-column="trainer">Trainer <span class="hvac-sort-indicator"></span></th>';
|
|
html += '<th class="hvac-sortable" data-column="date">Date <span class="hvac-sort-indicator"></span></th>';
|
|
html += '<th class="hvac-sortable" data-column="status">Status <span class="hvac-sort-indicator"></span></th>';
|
|
html += '<th class="hvac-sortable" data-column="capacity">Capacity <span class="hvac-sort-indicator"></span></th>';
|
|
html += '<th class="hvac-sortable" data-column="sold">Sold <span class="hvac-sort-indicator"></span></th>';
|
|
html += '<th class="hvac-sortable" data-column="revenue">Revenue <span class="hvac-sort-indicator"></span></th>';
|
|
html += '<th>Actions</th>';
|
|
html += '</tr></thead><tbody>';
|
|
|
|
events.forEach(function(event) {
|
|
html += '<tr>';
|
|
html += '<td><strong><a href="' + event.link + '" target="_blank">' + event.name + '</a></strong></td>';
|
|
html += '<td>' + event.trainer_name + '<br><small>' + event.trainer_email + '</small></td>';
|
|
html += '<td>' + event.date + '<br><small>' + event.time + '</small></td>';
|
|
html += '<td><span class="hvac-status-badge ' + event.status_class + '">' + event.status + '</span></td>';
|
|
html += '<td>' + event.capacity + '</td>';
|
|
html += '<td>' + event.sold + '</td>';
|
|
html += '<td>' + event.revenue + '</td>';
|
|
html += '<td>';
|
|
html += '<a href="' + event.link + '" class="hvac-btn hvac-btn-sm" target="_blank">View</a> ';
|
|
html += '<a href="' + event.trainer_edit_link + '" class="hvac-btn hvac-btn-sm hvac-btn-secondary" target="_blank">Edit</a>';
|
|
html += '</td>';
|
|
html += '</tr>';
|
|
});
|
|
|
|
html += '</tbody></table>';
|
|
}
|
|
|
|
// Add pagination
|
|
if (pagination.total_pages > 1) {
|
|
html += renderPagination(pagination);
|
|
}
|
|
|
|
html += '</div>';
|
|
$('#hvac-events-table-container').html(html);
|
|
}
|
|
|
|
/**
|
|
* Render pagination
|
|
*/
|
|
function renderPagination(pagination) {
|
|
var html = '<div class="hvac-pagination">';
|
|
|
|
// Previous button
|
|
if (pagination.has_prev) {
|
|
html += '<button type="button" class="hvac-pagination-btn hvac-btn hvac-btn-secondary" data-page="' + (pagination.current_page - 1) + '">« Previous</button>';
|
|
}
|
|
|
|
// Page numbers (show 5 pages around current)
|
|
var startPage = Math.max(1, pagination.current_page - 2);
|
|
var endPage = Math.min(pagination.total_pages, pagination.current_page + 2);
|
|
|
|
if (startPage > 1) {
|
|
html += '<button type="button" class="hvac-pagination-btn hvac-btn hvac-btn-secondary" data-page="1">1</button>';
|
|
if (startPage > 2) {
|
|
html += '<span class="hvac-pagination-dots">...</span>';
|
|
}
|
|
}
|
|
|
|
for (var i = startPage; i <= endPage; i++) {
|
|
var activeClass = i === pagination.current_page ? ' hvac-btn-primary' : ' hvac-btn-secondary';
|
|
html += '<button type="button" class="hvac-pagination-btn hvac-btn' + activeClass + '" data-page="' + i + '">' + i + '</button>';
|
|
}
|
|
|
|
if (endPage < pagination.total_pages) {
|
|
if (endPage < pagination.total_pages - 1) {
|
|
html += '<span class="hvac-pagination-dots">...</span>';
|
|
}
|
|
html += '<button type="button" class="hvac-pagination-btn hvac-btn hvac-btn-secondary" data-page="' + pagination.total_pages + '">' + pagination.total_pages + '</button>';
|
|
}
|
|
|
|
// Next button
|
|
if (pagination.has_next) {
|
|
html += '<button type="button" class="hvac-pagination-btn hvac-btn hvac-btn-secondary" data-page="' + (pagination.current_page + 1) + '">Next »</button>';
|
|
}
|
|
|
|
html += '</div>';
|
|
return html;
|
|
}
|
|
|
|
/**
|
|
* Render simple calendar view
|
|
*/
|
|
function renderCalendar(events) {
|
|
var html = '<div class="hvac-simple-calendar">';
|
|
|
|
if (events.length === 0) {
|
|
html += '<div class="hvac-no-events">';
|
|
html += '<p>No events found for the selected date range.</p>';
|
|
html += '</div>';
|
|
} else {
|
|
// Group events by date
|
|
var eventsByDate = {};
|
|
events.forEach(function(event) {
|
|
var date = event.start;
|
|
if (!eventsByDate[date]) {
|
|
eventsByDate[date] = [];
|
|
}
|
|
eventsByDate[date].push(event);
|
|
});
|
|
|
|
// Sort dates
|
|
var sortedDates = Object.keys(eventsByDate).sort();
|
|
|
|
sortedDates.forEach(function(date) {
|
|
var dateObj = new Date(date);
|
|
var dateStr = dateObj.toLocaleDateString('en-US', {
|
|
weekday: 'long',
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric'
|
|
});
|
|
|
|
html += '<div class="hvac-calendar-date-group">';
|
|
html += '<h3 class="hvac-calendar-date-header">' + dateStr + '</h3>';
|
|
html += '<div class="hvac-calendar-events">';
|
|
|
|
eventsByDate[date].forEach(function(event) {
|
|
html += '<div class="hvac-calendar-event-item ' + event.className + '">';
|
|
html += '<div class="hvac-event-title"><a href="' + event.url + '" target="_blank">' + event.title + '</a></div>';
|
|
html += '<div class="hvac-event-trainer">Trainer: ' + event.trainer + '</div>';
|
|
html += '<div class="hvac-event-details">';
|
|
html += 'Capacity: ' + event.extendedProps.capacity + ' | ';
|
|
html += 'Sold: ' + event.extendedProps.sold + ' | ';
|
|
html += 'Revenue: $' + formatMoney(event.extendedProps.revenue);
|
|
html += '</div>';
|
|
html += '</div>';
|
|
});
|
|
|
|
html += '</div></div>';
|
|
});
|
|
}
|
|
|
|
html += '</div>';
|
|
$('#hvac-calendar-content').html(html);
|
|
}
|
|
|
|
/**
|
|
* Update events count display
|
|
*/
|
|
function updateEventsCount(count) {
|
|
var text = count + ' event' + (count !== 1 ? 's' : '') + ' found';
|
|
$('#events-count-display').text(text);
|
|
}
|
|
|
|
/**
|
|
* Show error message
|
|
*/
|
|
function showError(message) {
|
|
// Create a simple alert for now - could be enhanced with a modal
|
|
if (window.console) {
|
|
console.error('HVAC Events Overview:', message);
|
|
}
|
|
|
|
// Add error message to page
|
|
var errorHtml = '<div class="hvac-notice hvac-notice-error" style="margin: 20px 0;">';
|
|
errorHtml += '<p><strong>Error:</strong> ' + message + '</p>';
|
|
errorHtml += '</div>';
|
|
|
|
// Show at top of events content
|
|
$('.hvac-events-content').prepend(errorHtml);
|
|
|
|
// Auto-remove after 5 seconds
|
|
setTimeout(function() {
|
|
$('.hvac-notice-error').fadeOut(function() {
|
|
$(this).remove();
|
|
});
|
|
}, 5000);
|
|
}
|
|
|
|
/**
|
|
* Format money for display
|
|
*/
|
|
function formatMoney(amount) {
|
|
return parseFloat(amount).toLocaleString('en-US', {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Debounce function to limit function calls
|
|
*/
|
|
function debounce(func, wait) {
|
|
var timeout;
|
|
return function executedFunction() {
|
|
var context = this;
|
|
var args = arguments;
|
|
var later = function() {
|
|
timeout = null;
|
|
func.apply(context, args);
|
|
};
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(later, wait);
|
|
};
|
|
}
|
|
|
|
})(jQuery); |