From c20b461e7d328287c0f29bb150ee3f7beb33e861 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 20 Aug 2025 13:34:15 -0300 Subject: [PATCH] feat: Implement secure Trainer Announcements system with comprehensive features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces a complete announcement management system for HVAC trainers with enterprise-grade security, performance optimization, and email notifications. ## Core Features - Custom post type for trainer announcements with categories and tags - Role-based permissions (master trainers can create/edit, all trainers can read) - AJAX-powered admin interface with real-time updates - Modal popup viewing for announcements on frontend - Automated email notifications when announcements are published - Google Drive integration for training resources ## Security Enhancements - Fixed critical capability mapping bug preventing proper permission checks - Added content disclosure protection for draft/private announcements - Fixed XSS vulnerabilities with proper output escaping and sanitization - Implemented permission checks on all AJAX endpoints - Added rate limiting to prevent abuse (30 requests/minute) - Email validation before sending notifications ## Performance Optimizations - Implemented intelligent caching for user queries (5-minute TTL) - Added cache versioning for announcement lists (2-minute TTL) - Automatic cache invalidation on content changes - Batch email processing to prevent timeouts (50 emails per batch) - Retry mechanism for failed email sends (max 3 attempts) ## Technical Implementation - Singleton pattern for all manager classes - WordPress coding standards compliance - Proper nonce verification on all AJAX requests - Comprehensive error handling and logging - Mobile-responsive UI with smooth animations - WCAG accessibility compliance ## Components Added - 6 PHP classes for modular architecture - 2 page templates (master announcements, trainer resources) - Admin and frontend JavaScript with jQuery integration - Comprehensive CSS for both admin and frontend - Email notification system with HTML templates - Complete documentation and implementation plans This system provides a secure, scalable foundation for trainer communications while following WordPress best practices and maintaining high code quality. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- assets/css/hvac-announcements-admin.css | 363 ++++++++++ assets/css/hvac-announcements.css | 645 ++++++++++++++++++ assets/js/hvac-announcements-admin.js | 499 ++++++++++++++ assets/js/hvac-announcements-view.js | 231 +++++++ ...AINER-ANNOUNCEMENTS-IMPLEMENTATION-PLAN.md | 248 +++++++ docs/TRAINER-ANNOUNCEMENTS-SPEC.md | 300 ++++++++ includes/class-hvac-announcements-ajax.php | 586 ++++++++++++++++ includes/class-hvac-announcements-cpt.php | 229 +++++++ includes/class-hvac-announcements-display.php | 431 ++++++++++++ includes/class-hvac-announcements-email.php | 425 ++++++++++++ includes/class-hvac-announcements-manager.php | 327 +++++++++ .../class-hvac-announcements-permissions.php | 362 ++++++++++ includes/class-hvac-menu-system.php | 11 + includes/class-hvac-plugin.php | 6 + templates/page-master-announcements.php | 194 ++++++ templates/page-trainer-resources.php | 188 +++++ 16 files changed, 5045 insertions(+) create mode 100644 assets/css/hvac-announcements-admin.css create mode 100644 assets/css/hvac-announcements.css create mode 100644 assets/js/hvac-announcements-admin.js create mode 100644 assets/js/hvac-announcements-view.js create mode 100644 docs/TRAINER-ANNOUNCEMENTS-IMPLEMENTATION-PLAN.md create mode 100644 docs/TRAINER-ANNOUNCEMENTS-SPEC.md create mode 100644 includes/class-hvac-announcements-ajax.php create mode 100644 includes/class-hvac-announcements-cpt.php create mode 100644 includes/class-hvac-announcements-display.php create mode 100644 includes/class-hvac-announcements-email.php create mode 100644 includes/class-hvac-announcements-manager.php create mode 100644 includes/class-hvac-announcements-permissions.php create mode 100644 templates/page-master-announcements.php create mode 100644 templates/page-trainer-resources.php diff --git a/assets/css/hvac-announcements-admin.css b/assets/css/hvac-announcements-admin.css new file mode 100644 index 00000000..6f3f0de5 --- /dev/null +++ b/assets/css/hvac-announcements-admin.css @@ -0,0 +1,363 @@ +/** + * HVAC Announcements Admin Styles + * + * @package HVAC_Community_Events + */ + +/* Page Layout */ +.hvac-master-announcements-page { + padding: 20px 0; +} + +.hvac-announcements-wrapper { + max-width: 1200px; + margin: 0 auto; + padding: 20px; + background: #fff; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Page Header */ +.page-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 30px; + padding-bottom: 20px; + border-bottom: 2px solid #e0e0e0; +} + +.page-header h1 { + margin: 0; + font-size: 28px; + color: #003366; +} + +#add-announcement-btn { + display: flex; + align-items: center; + gap: 5px; + padding: 10px 20px; + font-size: 14px; +} + +/* Controls */ +.announcements-controls { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + padding: 15px; + background: #f5f5f5; + border-radius: 4px; +} + +.filter-group { + display: flex; + align-items: center; + gap: 10px; +} + +.filter-select { + padding: 5px 10px; + border: 1px solid #ddd; + border-radius: 4px; +} + +.search-group { + display: flex; + gap: 5px; +} + +#announcement-search { + width: 250px; + padding: 5px 10px; + border: 1px solid #ddd; + border-radius: 4px; +} + +/* Table */ +.announcements-table-wrapper { + overflow-x: auto; +} + +#announcements-table { + width: 100%; + border-collapse: collapse; +} + +#announcements-table th { + background: #f0f0f0; + font-weight: 600; + text-align: left; + padding: 10px; +} + +#announcements-table td { + padding: 10px; + border-top: 1px solid #e0e0e0; +} + +#announcements-table .no-items { + text-align: center; + color: #666; + font-style: italic; +} + +/* Status badges */ +.status-publish { + display: inline-block; + padding: 2px 8px; + background: #4caf50; + color: white; + border-radius: 3px; + font-size: 12px; +} + +.status-draft { + display: inline-block; + padding: 2px 8px; + background: #ff9800; + color: white; + border-radius: 3px; + font-size: 12px; +} + +.status-private { + display: inline-block; + padding: 2px 8px; + background: #9c27b0; + color: white; + border-radius: 3px; + font-size: 12px; +} + +/* Column widths */ +.column-title { width: 30%; } +.column-status { width: 10%; } +.column-categories { width: 20%; } +.column-author { width: 15%; } +.column-date { width: 15%; } +.column-actions { width: 10%; text-align: right; } + +/* Action buttons */ +.column-actions .button-small { + padding: 2px 8px; + font-size: 12px; + margin-left: 5px; +} + +/* Pagination */ +.announcements-pagination { + display: flex; + justify-content: center; + align-items: center; + gap: 20px; + margin-top: 20px; + padding-top: 20px; + border-top: 1px solid #e0e0e0; +} + +.page-info { + color: #666; +} + +/* Modal */ +.hvac-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + z-index: 999999; + display: flex; + justify-content: center; + align-items: center; +} + +.modal-content { + background: white; + width: 90%; + max-width: 800px; + max-height: 90vh; + overflow-y: auto; + border-radius: 8px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + border-bottom: 1px solid #e0e0e0; +} + +.modal-header h2 { + margin: 0; + color: #003366; +} + +.modal-close { + background: none; + border: none; + font-size: 28px; + cursor: pointer; + color: #666; + padding: 0; + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; +} + +.modal-close:hover { + color: #000; +} + +.modal-body { + padding: 20px; +} + +.modal-footer { + display: flex; + justify-content: flex-end; + gap: 10px; + padding: 20px; + border-top: 1px solid #e0e0e0; + background: #f5f5f5; +} + +/* Form */ +.form-group { + margin-bottom: 20px; +} + +.form-group label { + display: block; + margin-bottom: 5px; + font-weight: 600; + color: #333; +} + +.form-group input[type="text"], +.form-group input[type="datetime-local"], +.form-group textarea, +.form-group select { + width: 100%; + padding: 8px 12px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; +} + +.form-group textarea { + resize: vertical; +} + +.form-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; +} + +.required { + color: #d32f2f; +} + +/* Categories */ +#categories-container { + display: flex; + flex-wrap: wrap; + gap: 15px; +} + +.category-checkbox { + display: flex; + align-items: center; + cursor: pointer; +} + +.category-checkbox input { + margin-right: 5px; +} + +/* Featured Image */ +.featured-image-container { + display: flex; + flex-direction: column; + gap: 10px; +} + +#featured-image-preview { + max-width: 300px; +} + +#featured-image-preview img { + max-width: 100%; + height: auto; + border: 1px solid #ddd; + border-radius: 4px; +} + +/* Notices */ +.notice { + padding: 12px; + margin: 10px 0; + border-left: 4px solid; + background: #fff; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04); +} + +.notice-success { + border-left-color: #4caf50; + background: #f0f8f0; +} + +.notice-error { + border-left-color: #d32f2f; + background: #fff5f5; +} + +/* Responsive */ +@media (max-width: 768px) { + .page-header { + flex-direction: column; + align-items: flex-start; + gap: 15px; + } + + .announcements-controls { + flex-direction: column; + gap: 15px; + } + + .search-group { + width: 100%; + } + + #announcement-search { + flex: 1; + } + + .form-row { + grid-template-columns: 1fr; + } + + .modal-content { + width: 95%; + margin: 20px; + } + + #announcements-table { + font-size: 14px; + } + + .column-categories, + .column-author { + display: none; + } +} \ No newline at end of file diff --git a/assets/css/hvac-announcements.css b/assets/css/hvac-announcements.css new file mode 100644 index 00000000..216eec0a --- /dev/null +++ b/assets/css/hvac-announcements.css @@ -0,0 +1,645 @@ +/** + * HVAC Announcements General Styles + * + * @package HVAC_Community_Events + */ + +/* Trainer Resources Page */ +.hvac-trainer-resources-page { + padding: 20px 0; +} + +.hvac-resources-wrapper { + max-width: 1200px; + margin: 0 auto; +} + +.page-description { + color: #666; + font-size: 16px; + margin-top: 10px; +} + +/* Resources Sections */ +.resources-section { + margin-bottom: 50px; + background: #fff; + border-radius: 8px; + padding: 30px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.section-title { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 25px; + padding-bottom: 15px; + border-bottom: 2px solid #003366; + color: #003366; + font-size: 24px; +} + +.section-title .dashicons { + font-size: 28px; + width: 28px; + height: 28px; +} + +/* Announcements Timeline */ +.hvac-announcements-timeline { + position: relative; +} + +.timeline-wrapper { + position: relative; + padding-left: 40px; +} + +.timeline-wrapper::before { + content: ''; + position: absolute; + left: 15px; + top: 0; + bottom: 0; + width: 2px; + background: #e0e0e0; +} + +.timeline-item { + position: relative; + margin-bottom: 40px; +} + +.timeline-marker { + position: absolute; + left: -30px; + top: 5px; + width: 12px; + height: 12px; + border-radius: 50%; + background: #003366; + border: 3px solid #fff; + box-shadow: 0 0 0 2px #e0e0e0; +} + +.timeline-content { + background: #f9f9f9; + padding: 20px; + border-radius: 8px; + border: 1px solid #e0e0e0; +} + +.timeline-header { + margin-bottom: 15px; +} + +.timeline-title { + margin: 0 0 10px 0; + font-size: 20px; +} + +.timeline-title a { + color: #003366; + text-decoration: none; +} + +.timeline-title a:hover { + color: #0056b3; + text-decoration: underline; +} + +.timeline-meta { + display: flex; + gap: 15px; + font-size: 14px; + color: #666; +} + +.timeline-thumbnail { + margin: 15px 0; +} + +.timeline-thumbnail img { + max-width: 100%; + height: auto; + border-radius: 4px; +} + +.timeline-excerpt { + margin: 15px 0; + line-height: 1.6; +} + +.timeline-categories { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 15px; +} + +.category-badge { + display: inline-block; + padding: 4px 10px; + background: #003366; + color: white; + border-radius: 15px; + font-size: 12px; +} + +.timeline-pagination { + text-align: center; + margin-top: 30px; +} + +.load-more-announcements { + padding: 10px 30px; + background: #003366; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; +} + +.load-more-announcements:hover { + background: #0056b3; +} + +.no-announcements { + text-align: center; + padding: 40px; + color: #666; + font-style: italic; +} + +/* Announcements List */ +.hvac-announcements-list { + margin: 20px 0; +} + +.announcements-list { + list-style: none; + padding: 0; + margin: 0; +} + +.announcement-item { + padding: 20px 0; + border-bottom: 1px solid #e0e0e0; +} + +.announcement-item:last-child { + border-bottom: none; +} + +.announcement-title { + margin: 0 0 10px 0; + font-size: 18px; +} + +.announcement-meta { + display: flex; + gap: 15px; + font-size: 14px; + color: #666; + margin-bottom: 10px; +} + +.announcement-excerpt { + line-height: 1.6; + color: #333; +} + +/* Google Drive Section */ +.google-drive-description { + margin-bottom: 20px; + color: #666; +} + +.google-drive-container { + background: #f5f5f5; + padding: 20px; + border-radius: 4px; +} + +.google-drive-iframe { + background: white; + border: 1px solid #ddd; + border-radius: 4px; +} + +.google-drive-footer { + text-align: center; + margin-top: 20px; +} + +.google-drive-footer .button { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 10px 20px; + background: #003366; + color: white; + text-decoration: none; + border-radius: 4px; +} + +.google-drive-footer .button:hover { + background: #0056b3; +} + +.help-text { + margin-top: 10px; + color: #666; + font-size: 14px; +} + +/* Quick Links Grid */ +.quick-links-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 20px; + margin-top: 20px; +} + +.resource-card { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding: 30px 20px; + background: #f9f9f9; + border: 1px solid #e0e0e0; + border-radius: 8px; + text-decoration: none; + color: #333; + transition: all 0.3s ease; +} + +.resource-card:hover { + background: #fff; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + transform: translateY(-2px); +} + +.resource-card .dashicons { + font-size: 48px; + width: 48px; + height: 48px; + color: #003366; + margin-bottom: 15px; +} + +.resource-card h3 { + margin: 0 0 10px 0; + font-size: 18px; + color: #003366; +} + +.resource-card p { + margin: 0; + color: #666; + font-size: 14px; +} + +/* Announcement Modal (for viewing) */ +.announcement-full { + padding: 20px; +} + +.announcement-header { + margin-bottom: 20px; + padding-bottom: 15px; + border-bottom: 2px solid #e0e0e0; +} + +.announcement-header h2 { + margin: 0 0 10px 0; + color: #003366; +} + +.announcement-featured-image { + margin: 20px 0; + text-align: center; +} + +.announcement-featured-image img { + max-width: 100%; + height: auto; + border-radius: 4px; +} + +.announcement-content { + line-height: 1.6; + color: #333; +} + +.announcement-content h1, +.announcement-content h2, +.announcement-content h3, +.announcement-content h4, +.announcement-content h5, +.announcement-content h6 { + color: #003366; + margin-top: 25px; + margin-bottom: 15px; +} + +.announcement-footer { + margin-top: 30px; + padding-top: 20px; + border-top: 1px solid #e0e0e0; + color: #666; + font-size: 14px; +} + +/* Modal Styles */ +.hvac-modal { + display: none; + position: fixed; + z-index: 999999; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0, 0, 0, 0.6); + animation: fadeIn 0.3s; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +.hvac-modal .modal-content { + background-color: #fefefe; + margin: 40px auto; + padding: 0; + border-radius: 8px; + width: 90%; + max-width: 900px; + max-height: 90vh; + overflow-y: auto; + box-shadow: 0 5px 30px rgba(0, 0, 0, 0.3); + position: relative; + animation: slideIn 0.3s; +} + +@keyframes slideIn { + from { + transform: translateY(-30px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +.hvac-modal .modal-close { + color: #aaa; + position: absolute; + top: 15px; + right: 20px; + font-size: 32px; + font-weight: bold; + cursor: pointer; + z-index: 10; + background: white; + border-radius: 50%; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); + transition: all 0.3s ease; +} + +.hvac-modal .modal-close:hover, +.hvac-modal .modal-close:focus { + color: #003366; + transform: rotate(90deg); +} + +.hvac-modal .modal-body { + padding: 30px; +} + +/* Loading state */ +.modal-loading { + text-align: center; + padding: 60px 20px; +} + +.modal-loading .spinner { + display: inline-block; + width: 40px; + height: 40px; + border: 4px solid #f3f3f3; + border-top: 4px solid #003366; + border-radius: 50%; + animation: spin 1s linear infinite; + margin-bottom: 20px; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.modal-loading p { + color: #666; + font-size: 16px; + margin: 0; +} + +/* Error state */ +.modal-error { + text-align: center; + padding: 40px 20px; + color: #d32f2f; +} + +.modal-error p { + margin: 0; + font-size: 16px; +} + +/* Body state when modal is open */ +body.modal-open { + overflow: hidden; +} + +/* Announcement content in modal */ +.hvac-modal .announcement-full { + padding: 0; +} + +.hvac-modal .announcement-header { + margin-bottom: 25px; + padding-bottom: 20px; + border-bottom: 2px solid #003366; +} + +.hvac-modal .announcement-header h2 { + margin: 0 40px 15px 0; + color: #003366; + font-size: 28px; + line-height: 1.3; +} + +.hvac-modal .announcement-meta { + display: flex; + gap: 20px; + font-size: 14px; + color: #666; +} + +.hvac-modal .announcement-meta span { + display: flex; + align-items: center; + gap: 5px; +} + +.hvac-modal .announcement-featured-image { + margin: 25px 0; + text-align: center; +} + +.hvac-modal .announcement-featured-image img { + max-width: 100%; + height: auto; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.hvac-modal .announcement-content { + font-size: 16px; + line-height: 1.7; + color: #333; +} + +.hvac-modal .announcement-content h1, +.hvac-modal .announcement-content h2, +.hvac-modal .announcement-content h3, +.hvac-modal .announcement-content h4, +.hvac-modal .announcement-content h5, +.hvac-modal .announcement-content h6 { + color: #003366; + margin-top: 30px; + margin-bottom: 15px; + font-weight: 600; +} + +.hvac-modal .announcement-content p { + margin-bottom: 15px; +} + +.hvac-modal .announcement-content ul, +.hvac-modal .announcement-content ol { + margin: 0 0 20px 20px; + padding-left: 20px; +} + +.hvac-modal .announcement-content li { + margin-bottom: 8px; +} + +.hvac-modal .announcement-content a { + color: #0056b3; + text-decoration: underline; +} + +.hvac-modal .announcement-content a:hover { + color: #003366; +} + +.hvac-modal .announcement-content blockquote { + margin: 20px 0; + padding: 15px 20px; + background: #f5f5f5; + border-left: 4px solid #003366; + font-style: italic; +} + +.hvac-modal .announcement-footer { + margin-top: 35px; + padding-top: 20px; + border-top: 1px solid #e0e0e0; + color: #666; + font-size: 14px; +} + +.hvac-modal .announcement-footer strong { + color: #333; + margin-right: 5px; +} + +/* Make announcement links look clickable */ +.announcement-link { + cursor: pointer; + transition: color 0.2s ease; +} + +.announcement-link:hover { + color: #0056b3 !important; +} + +/* Responsive */ +@media (max-width: 768px) { + .timeline-wrapper { + padding-left: 20px; + } + + .timeline-wrapper::before { + left: 5px; + } + + .timeline-marker { + left: -20px; + } + + .quick-links-grid { + grid-template-columns: 1fr; + } + + .section-title { + font-size: 20px; + } + + .google-drive-iframe { + height: 400px !important; + } + + /* Modal responsive styles */ + .hvac-modal .modal-content { + margin: 20px auto; + width: 95%; + max-height: 95vh; + } + + .hvac-modal .modal-body { + padding: 20px; + } + + .hvac-modal .modal-close { + top: 10px; + right: 10px; + width: 35px; + height: 35px; + font-size: 28px; + } + + .hvac-modal .announcement-header h2 { + font-size: 24px; + margin-right: 35px; + } + + .hvac-modal .announcement-meta { + flex-direction: column; + gap: 10px; + } +} \ No newline at end of file diff --git a/assets/js/hvac-announcements-admin.js b/assets/js/hvac-announcements-admin.js new file mode 100644 index 00000000..c73d9857 --- /dev/null +++ b/assets/js/hvac-announcements-admin.js @@ -0,0 +1,499 @@ +/** + * HVAC Announcements Admin JavaScript + * + * @package HVAC_Community_Events + */ + +jQuery(document).ready(function($) { + 'use strict'; + + // State variables + let currentPage = 1; + let totalPages = 1; + let currentStatus = 'any'; + let searchTerm = ''; + let editorInstance = null; + + // Initialize + init(); + + /** + * Initialize the announcements interface + */ + function init() { + loadAnnouncements(); + loadCategories(); + initializeEventHandlers(); + initializeEditor(); + } + + /** + * Initialize TinyMCE editor + */ + function initializeEditor() { + if (typeof wp !== 'undefined' && wp.editor) { + // Initialize WordPress editor + wp.editor.initialize('announcement-content', { + tinymce: { + wpautop: true, + plugins: 'lists link image media paste', + toolbar1: 'formatselect | bold italic | alignleft aligncenter alignright | bullist numlist | link unlink | wp_adv', + toolbar2: 'strikethrough hr forecolor pastetext removeformat charmap outdent indent undo redo wp_help', + height: 300 + }, + quicktags: true, + mediaButtons: true + }); + } + } + + /** + * Initialize event handlers + */ + function initializeEventHandlers() { + // Add announcement button + $('#add-announcement-btn').on('click', function() { + openModal(); + }); + + // Modal close buttons + $('.modal-close, .modal-cancel').on('click', function() { + closeModal(); + }); + + // Form submission + $('#announcement-form').on('submit', function(e) { + e.preventDefault(); + saveAnnouncement(); + }); + + // Status filter + $('#status-filter').on('change', function() { + currentStatus = $(this).val(); + currentPage = 1; + loadAnnouncements(); + }); + + // Search + $('#search-btn').on('click', function() { + searchTerm = $('#announcement-search').val(); + currentPage = 1; + loadAnnouncements(); + }); + + $('#announcement-search').on('keypress', function(e) { + if (e.which === 13) { + searchTerm = $(this).val(); + currentPage = 1; + loadAnnouncements(); + } + }); + + // Pagination + $('#prev-page').on('click', function() { + if (currentPage > 1) { + currentPage--; + loadAnnouncements(); + } + }); + + $('#next-page').on('click', function() { + if (currentPage < totalPages) { + currentPage++; + loadAnnouncements(); + } + }); + + // Edit/Delete actions (delegated) + $(document).on('click', '.edit-announcement', function() { + const id = $(this).data('id'); + editAnnouncement(id); + }); + + $(document).on('click', '.delete-announcement', function() { + const id = $(this).data('id'); + if (confirm(hvac_announcements.strings.confirm_delete)) { + deleteAnnouncement(id); + } + }); + + // Featured image selection + $('#select-featured-image').on('click', function(e) { + e.preventDefault(); + selectFeaturedImage(); + }); + + $('#remove-featured-image').on('click', function(e) { + e.preventDefault(); + removeFeaturedImage(); + }); + } + + /** + * Load announcements via AJAX + */ + function loadAnnouncements() { + const data = { + action: 'hvac_get_announcements', + nonce: hvac_announcements.nonce, + page: currentPage, + per_page: 20, + status: currentStatus, + search: searchTerm + }; + + $.post(hvac_announcements.ajax_url, data, function(response) { + if (response.success) { + displayAnnouncements(response.data.announcements); + updatePagination(response.data.current_page, response.data.pages); + } else { + showError(response.data || hvac_announcements.strings.error_loading); + } + }); + } + + /** + * Display announcements in table + */ + function displayAnnouncements(announcements) { + const tbody = $('#announcements-list'); + tbody.empty(); + + if (announcements.length === 0) { + tbody.append('No announcements found'); + return; + } + + announcements.forEach(function(announcement) { + const row = $(''); + + // Title + row.append('' + escapeHtml(announcement.title) + ''); + + // Status + const statusClass = 'status-' + announcement.status; + row.append('' + announcement.status + ''); + + // Categories + const categories = announcement.categories.join(', ') || '-'; + row.append('' + escapeHtml(categories) + ''); + + // Author + row.append('' + escapeHtml(announcement.author) + ''); + + // Date + row.append('' + announcement.date + ''); + + // Actions + let actions = ''; + if (announcement.can_edit) { + actions += ' '; + } + if (announcement.can_delete) { + actions += ''; + } + actions += ''; + row.append(actions); + + tbody.append(row); + }); + } + + /** + * Update pagination controls + */ + function updatePagination(current, total) { + currentPage = current; + totalPages = total; + + $('#current-page').text(current); + $('#total-pages').text(total); + + $('#prev-page').prop('disabled', current <= 1); + $('#next-page').prop('disabled', current >= total); + } + + /** + * Load categories for the form + */ + function loadCategories() { + $.post(hvac_announcements.ajax_url, { + action: 'hvac_get_announcement_categories', + nonce: hvac_announcements.nonce + }, function(response) { + if (response.success) { + displayCategories(response.data); + } + }); + } + + /** + * Display categories as checkboxes + */ + function displayCategories(categories) { + const container = $('#categories-container'); + container.empty(); + + if (categories.length === 0) { + container.append('

No categories available

'); + return; + } + + categories.forEach(function(category) { + const checkbox = $('