upskill-event-manager/assets/js/hvac-trainer-communication-templates.js
Ben a74c273b1d feat: complete master trainer area audit and implementation
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>
2025-08-23 09:56:42 -03:00

568 lines
No EOL
22 KiB
JavaScript

/**
* HVAC Trainer Communication Templates JavaScript
*
* Handles read-only communication templates functionality:
* - Accordion expand/collapse
* - Copy to clipboard
* - Search and filtering
* - Modal preview
* - AJAX template loading
*
* @package HVAC_Community_Events
* @since 2.0.0
*/
jQuery(document).ready(function($) {
'use strict';
// Initialize the communication templates interface
const HVACTemplates = {
// Configuration
config: {
copyTimeout: 2000,
searchDelay: 500,
loadMoreCount: 6,
},
// State
state: {
currentPage: 1,
isLoading: false,
searchTimeout: null,
copiedTimeout: null,
},
// Initialize all functionality
init: function() {
this.bindEvents();
this.initializeTooltips();
this.handleInitialLoad();
},
// Bind all event handlers
bindEvents: function() {
// Template expand/collapse
$(document).on('click', '.hvac-template-expand', this.toggleTemplate.bind(this));
// Copy to clipboard
$(document).on('click', '.hvac-template-copy', this.copyTemplate.bind(this));
// Preview modal
$(document).on('click', '.hvac-template-preview-btn', this.showPreview.bind(this));
// Modal controls
$(document).on('click', '.hvac-modal-close', this.closeModal.bind(this));
$(document).on('click', '.hvac-modal-overlay', function(e) {
if (e.target === this) {
HVACTemplates.closeModal();
}
});
// Search and filters
$('#hvac-template-search').on('input', this.debounceSearch.bind(this));
$('#hvac-template-category, #hvac-template-channel').on('change', this.performSearch.bind(this));
$('.hvac-search-button').on('click', this.performSearch.bind(this));
// Load more functionality
$(document).on('click', '.hvac-load-more', this.loadMoreTemplates.bind(this));
// Keyboard accessibility
$(document).on('keydown', this.handleKeyboard.bind(this));
// Token click to copy
$(document).on('click', '.hvac-token', this.copyToken.bind(this));
},
// Handle initial page load
handleInitialLoad: function() {
// Hide templates beyond the initial count for load more functionality
const $templates = $('.hvac-template-card');
if ($templates.length > this.config.loadMoreCount) {
$templates.slice(this.config.loadMoreCount).hide();
$('.hvac-load-more').show();
}
// Apply any URL-based filters
this.applyUrlFilters();
},
// Toggle template expand/collapse
toggleTemplate: function(e) {
e.preventDefault();
const $button = $(e.currentTarget);
const $card = $button.closest('.hvac-template-card');
const $preview = $card.find('.hvac-template-preview');
const $fullContent = $card.find('.hvac-template-full-content');
const $expandText = $button.find('.hvac-expand-text');
const $collapseText = $button.find('.hvac-collapse-text');
const $icon = $button.find('.dashicons');
if ($fullContent.is(':visible')) {
// Collapse
$fullContent.slideUp(300);
$preview.slideDown(300);
$expandText.show();
$collapseText.hide();
$icon.removeClass('dashicons-minus').addClass('dashicons-plus-alt2');
$button.attr('aria-expanded', 'false');
} else {
// Expand
$preview.slideUp(300);
$fullContent.slideDown(300);
$expandText.hide();
$collapseText.show();
$icon.removeClass('dashicons-plus-alt2').addClass('dashicons-minus');
$button.attr('aria-expanded', 'true');
}
},
// Copy template to clipboard
copyTemplate: function(e) {
e.preventDefault();
const $button = $(e.currentTarget);
const $card = $button.closest('.hvac-template-card');
const templateId = $button.data('template-id');
// Get template content (prefer full content if expanded)
const $fullContent = $card.find('.hvac-template-full-content');
let content;
if ($fullContent.is(':visible')) {
content = $fullContent.text().trim();
} else {
// Get content from data or make AJAX call
content = this.getTemplateContent(templateId);
}
if (content) {
this.copyToClipboard(content, $button);
}
},
// Get template content (from DOM or AJAX)
getTemplateContent: function(templateId) {
const $card = $('[data-template-id="' + templateId + '"]');
const $fullContent = $card.find('.hvac-template-full-content');
if ($fullContent.length) {
return $fullContent.text().trim();
}
// Fallback to preview content
const $preview = $card.find('.hvac-template-preview');
return $preview.text().trim();
},
// Copy text to clipboard with fallback
copyToClipboard: function(text, $button) {
const $copyText = $button.find('.hvac-copy-text');
const $copiedText = $button.find('.hvac-copied-text');
// Modern clipboard API
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(() => {
this.showCopySuccess($copyText, $copiedText);
}).catch(() => {
this.fallbackCopyToClipboard(text, $copyText, $copiedText);
});
} else {
this.fallbackCopyToClipboard(text, $copyText, $copiedText);
}
},
// Fallback copy method
fallbackCopyToClipboard: function(text, $copyText, $copiedText) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
if (successful) {
this.showCopySuccess($copyText, $copiedText);
} else {
this.showCopyError();
}
} catch (err) {
console.error('Copy failed:', err);
this.showCopyError();
} finally {
document.body.removeChild(textArea);
}
},
// Show copy success state
showCopySuccess: function($copyText, $copiedText) {
$copyText.hide();
$copiedText.show();
// Clear any existing timeout
if (this.state.copiedTimeout) {
clearTimeout(this.state.copiedTimeout);
}
// Reset after timeout
this.state.copiedTimeout = setTimeout(() => {
$copiedText.hide();
$copyText.show();
}, this.config.copyTimeout);
},
// Show copy error
showCopyError: function() {
const message = hvacTrainerTemplates.strings.copyError || 'Copy failed. Please select and copy manually.';
this.showNotification(message, 'error');
},
// Show template preview modal
showPreview: function(e) {
e.preventDefault();
const templateId = $(e.currentTarget).data('template-id');
const $modal = $('#hvac-template-modal');
const $content = $('#hvac-modal-content');
if (!templateId) return;
// Show loading state
$content.html('<div class="hvac-loading"><div class="hvac-spinner"></div><p>' + (hvacTrainerTemplates.strings.loading || 'Loading...') + '</p></div>');
$modal.show().attr('aria-hidden', 'false');
$('body').addClass('hvac-modal-open');
// Focus modal for accessibility
$modal.find('.hvac-modal-container').focus();
// Get template data via AJAX
$.ajax({
url: hvacTrainerTemplates.ajaxUrl,
type: 'POST',
data: {
action: 'hvac_get_template_preview',
template_id: templateId,
nonce: hvacTrainerTemplates.nonce
},
success: (response) => {
if (response.success && response.data) {
this.renderPreviewContent(response.data, $content);
} else {
$content.html('<div class="hvac-error">Failed to load template preview.</div>');
}
},
error: () => {
$content.html('<div class="hvac-error">Network error occurred while loading template.</div>');
}
});
},
// Render preview content in modal
renderPreviewContent: function(template, $content) {
let html = '<div class="hvac-template-preview-content">';
html += '<h3>' + $('<div>').text(template.title).html() + '</h3>';
// Template meta
html += '<div class="hvac-template-meta">';
if (template.categories && template.categories.length) {
html += '<span class="hvac-meta-category"><strong>Category:</strong> ' + template.categories.map(cat => cat.name).join(', ') + '</span>';
}
if (template.channels && template.channels.length) {
html += '<span class="hvac-meta-channel"><strong>Channel:</strong> ' + template.channels.map(ch => ch.name).join(', ') + '</span>';
}
html += '</div>';
// Template content
html += '<div class="hvac-template-content-preview">';
html += '<pre>' + $('<div>').text(template.content).html() + '</pre>';
html += '</div>';
// Allowed tokens
if (template.allowed_tokens) {
html += '<div class="hvac-template-tokens-preview">';
html += '<h4>Available Tokens:</h4>';
html += '<div class="hvac-tokens-list">';
const tokens = template.allowed_tokens.split(', ');
tokens.forEach(token => {
html += '<span class="hvac-token">' + $('<div>').text(token).html() + '</span>';
});
html += '</div></div>';
}
html += '</div>';
$content.html(html);
// Update copy button
$('.hvac-copy-template').data('template-content', template.content);
},
// Close modal
closeModal: function() {
const $modal = $('#hvac-template-modal');
$modal.hide().attr('aria-hidden', 'true');
$('body').removeClass('hvac-modal-open');
},
// Debounced search
debounceSearch: function() {
if (this.state.searchTimeout) {
clearTimeout(this.state.searchTimeout);
}
this.state.searchTimeout = setTimeout(() => {
this.performSearch();
}, this.config.searchDelay);
},
// Perform search and filtering
performSearch: function() {
if (this.state.isLoading) return;
this.state.isLoading = true;
const $loading = $('#hvac-templates-loading');
const $list = $('#hvac-templates-list');
const $empty = $('#hvac-templates-empty');
// Show loading state
$loading.show();
$list.hide();
$empty.hide();
const searchData = {
action: 'hvac_search_templates',
search: $('#hvac-template-search').val(),
category: $('#hvac-template-category').val(),
channel: $('#hvac-template-channel').val(),
nonce: hvacTrainerTemplates.nonce
};
$.ajax({
url: hvacTrainerTemplates.ajaxUrl,
type: 'POST',
data: searchData,
success: (response) => {
if (response.success) {
this.updateTemplatesList(response.data.templates);
} else {
this.showError('Search failed. Please try again.');
}
},
error: () => {
this.showError('Network error occurred during search.');
},
complete: () => {
this.state.isLoading = false;
$loading.hide();
$list.show();
}
});
},
// Update templates list with search results
updateTemplatesList: function(templates) {
const $list = $('#hvac-templates-list');
const $empty = $('#hvac-templates-empty');
if (templates.length === 0) {
$list.hide();
$empty.show();
return;
}
// Re-render templates (simplified version)
let html = '<div class="hvac-templates-grid">';
templates.forEach(template => {
html += this.renderTemplateCard(template);
});
html += '</div>';
$list.html(html).show();
$empty.hide();
// Reset pagination
this.state.currentPage = 1;
},
// Render a single template card
renderTemplateCard: function(template) {
let html = '<div class="hvac-template-card" data-template-id="' + template.id + '">';
// Header
html += '<div class="hvac-template-header">';
html += '<h3 class="hvac-template-title">' + $('<div>').text(template.title).html() + '</h3>';
// Meta information
html += '<div class="hvac-template-meta">';
if (template.channels && template.channels.length) {
const channel = template.channels[0];
const icon = channel.slug === 'email' ? 'email' : 'smartphone';
html += '<span class="hvac-template-channel hvac-template-channel-' + channel.slug + '">';
html += '<span class="dashicons dashicons-' + icon + '"></span>' + channel.name;
html += '</span>';
}
if (template.categories && template.categories.length) {
html += '<span class="hvac-template-category">' + template.categories.map(cat => cat.name).join(', ') + '</span>';
}
html += '</div>';
html += '</div>';
// Excerpt
if (template.excerpt) {
html += '<div class="hvac-template-excerpt"><p>' + $('<div>').text(template.excerpt).html() + '</p></div>';
}
// Content
html += '<div class="hvac-template-content">';
html += '<div class="hvac-template-preview">' + this.truncateText(template.content, 20) + '</div>';
html += '<div class="hvac-template-full-content" style="display: none;">' + $('<div>').text(template.content).html().replace(/\n/g, '<br>') + '</div>';
html += '</div>';
// Actions
html += '<div class="hvac-template-actions">';
html += '<button type="button" class="hvac-template-expand button button-secondary">';
html += '<span class="dashicons dashicons-plus-alt2"></span>';
html += '<span class="hvac-expand-text">Show Full Template</span>';
html += '<span class="hvac-collapse-text" style="display: none;">Show Less</span>';
html += '</button>';
html += '<button type="button" class="hvac-template-copy button button-primary" data-template-id="' + template.id + '">';
html += '<span class="dashicons dashicons-clipboard"></span>';
html += '<span class="hvac-copy-text">Copy Template</span>';
html += '<span class="hvac-copied-text" style="display: none;">Copied!</span>';
html += '</button>';
html += '</div>';
// Tokens
if (template.allowed_tokens) {
html += '<div class="hvac-template-tokens">';
html += '<h4>Available Tokens:</h4>';
html += '<div class="hvac-tokens-list">';
const tokens = template.allowed_tokens.split(', ');
tokens.forEach(token => {
html += '<span class="hvac-token">' + $('<div>').text(token).html() + '</span>';
});
html += '</div></div>';
}
html += '</div>';
return html;
},
// Load more templates
loadMoreTemplates: function(e) {
e.preventDefault();
const $button = $(e.currentTarget);
const $hiddenTemplates = $('.hvac-template-card:hidden');
const nextBatch = $hiddenTemplates.slice(0, this.config.loadMoreCount);
if (nextBatch.length === 0) {
$button.hide();
return;
}
nextBatch.fadeIn(300);
this.state.currentPage++;
// Hide button if no more templates
if (nextBatch.length < this.config.loadMoreCount || $hiddenTemplates.length <= this.config.loadMoreCount) {
$button.hide();
}
},
// Copy individual token
copyToken: function(e) {
e.preventDefault();
const token = $(e.currentTarget).text();
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(token).then(() => {
this.showNotification('Token copied: ' + token, 'success');
}).catch(() => {
this.showNotification('Failed to copy token', 'error');
});
}
},
// Handle keyboard navigation
handleKeyboard: function(e) {
// Close modal with Escape
if (e.key === 'Escape' && $('#hvac-template-modal').is(':visible')) {
this.closeModal();
e.preventDefault();
}
// Quick search with /
if (e.key === '/' && !$(e.target).is(':input')) {
$('#hvac-template-search').focus();
e.preventDefault();
}
},
// Apply URL-based filters
applyUrlFilters: function() {
const urlParams = new URLSearchParams(window.location.search);
const category = urlParams.get('category');
const channel = urlParams.get('channel');
const search = urlParams.get('search');
if (category) $('#hvac-template-category').val(category);
if (channel) $('#hvac-template-channel').val(channel);
if (search) $('#hvac-template-search').val(search);
if (category || channel || search) {
this.performSearch();
}
},
// Initialize tooltips (if tooltip library is available)
initializeTooltips: function() {
// Implementation depends on available tooltip library
},
// Show notification
showNotification: function(message, type) {
// Create notification element
const $notification = $('<div class="hvac-notification hvac-notification-' + type + '">' + message + '</div>');
$('body').append($notification);
$notification.fadeIn(300).delay(3000).fadeOut(300, function() {
$(this).remove();
});
},
// Show error message
showError: function(message) {
this.showNotification(message, 'error');
},
// Truncate text helper
truncateText: function(text, wordLimit) {
const words = text.split(' ');
if (words.length <= wordLimit) return text;
return words.slice(0, wordLimit).join(' ') + '...';
}
};
// Initialize when DOM is ready
HVACTemplates.init();
// Handle modal copy button
$(document).on('click', '.hvac-copy-template', function(e) {
if ($(this).closest('#hvac-template-modal').length) {
e.preventDefault();
const content = $(this).data('template-content');
if (content) {
HVACTemplates.copyToClipboard(content, $(this));
}
}
});
});