/** * HVAC Bulk Operations JavaScript * * Handles client-side bulk event operations including progress tracking, * batch creation, and template application workflows. * * @package HVAC_Community_Events * @since 3.1.0 (Phase 2A) */ (function($) { 'use strict'; // Global bulk operations management object window.HVACBulkOperations = { activeOperations: new Map(), progressPollingInterval: null, pollingFrequency: 2000, // 2 seconds /** * Initialize bulk operations functionality */ init: function() { this.bindEvents(); this.initializeProgressTracking(); }, /** * Bind event handlers */ bindEvents: function() { // Bulk creation from template $(document).on('click', '.hvac-bulk-create-btn', this.handleBulkCreateFromTemplate.bind(this)); // Template application to events $(document).on('click', '.hvac-apply-template-bulk-btn', this.handleTemplateApplicationBulk.bind(this)); // Cancel operation $(document).on('click', '.hvac-cancel-operation-btn', this.handleCancelOperation.bind(this)); // Show bulk variations modal $(document).on('click', '.hvac-show-bulk-variations', this.showBulkVariationsModal.bind(this)); // Add variation row $(document).on('click', '.hvac-add-variation', this.addVariationRow.bind(this)); // Remove variation row $(document).on('click', '.hvac-remove-variation', this.removeVariationRow.bind(this)); // Progress modal close $(document).on('click', '.hvac-close-progress-modal', this.closeProgressModal.bind(this)); // Event selection for bulk operations $(document).on('change', '.hvac-bulk-event-checkbox', this.handleEventSelection.bind(this)); $(document).on('change', '.hvac-bulk-select-all', this.handleSelectAll.bind(this)); }, /** * Initialize progress tracking for any existing operations */ initializeProgressTracking: function() { // Check if there are any operations in progress from localStorage const savedOperations = this.getSavedOperations(); if (savedOperations.length > 0) { savedOperations.forEach(operationId => { this.trackOperation(operationId); }); this.startProgressPolling(); } }, /** * Handle bulk event creation from template */ handleBulkCreateFromTemplate: function(event) { event.preventDefault(); const button = $(event.target); const templateId = button.data('template-id'); if (!templateId) { this.showMessage('No template selected for bulk creation', 'error'); return; } // Show bulk variations modal this.showBulkVariationsModal(templateId); }, /** * Show bulk variations modal for event creation */ showBulkVariationsModal: function(templateId) { const modal = $('#hvac-bulk-variations-modal'); if (!modal.length) { this.createBulkVariationsModal(); } // Set template ID $('#bulk-template-id').val(templateId); // Clear existing variations $('.bulk-variations-container').empty(); // Add initial variation rows this.addVariationRow(); this.addVariationRow(); // Show modal $('#hvac-bulk-variations-modal').removeClass('hidden'); }, /** * Create bulk variations modal HTML */ createBulkVariationsModal: function() { const modalHtml = ` `; $('body').append(modalHtml); // Bind form submission $(document).on('submit', '#hvac-bulk-variations-form', this.submitBulkCreation.bind(this)); }, /** * Add a variation row to the bulk modal */ addVariationRow: function() { const container = $('.bulk-variations-container'); const variationIndex = container.find('.variation-row').length + 1; const rowHtml = `

Event ${variationIndex}

This will be appended to the template description.
`; container.append(rowHtml); }, /** * Remove a variation row */ removeVariationRow: function(event) { event.preventDefault(); const row = $(event.target).closest('.variation-row'); const container = $('.bulk-variations-container'); // Don't allow removing the last row if (container.find('.variation-row').length <= 1) { this.showMessage('At least one event variation is required', 'error'); return; } row.fadeOut(300, function() { $(this).remove(); // Renumber remaining rows this.renumberVariationRows(); }.bind(this)); }, /** * Renumber variation rows after removal */ renumberVariationRows: function() { $('.variation-row').each(function(index) { const newIndex = index + 1; const row = $(this); row.attr('data-variation-index', newIndex); row.find('h4').text('Event ' + newIndex); // Update form field names and IDs row.find('input, textarea').each(function() { const field = $(this); const name = field.attr('name'); const id = field.attr('id'); if (name) { const newName = name.replace(/variations\[\d+\]/, `variations[${newIndex}]`); field.attr('name', newName); } if (id) { const newId = id.replace(/variation_\d+_/, `variation_${newIndex}_`); field.attr('id', newId); // Update corresponding label row.find(`label[for="${id}"]`).attr('for', newId); } }); }); }, /** * Submit bulk event creation */ submitBulkCreation: function(event) { event.preventDefault(); const form = $(event.target); const submitButton = form.find('button[type="submit"]'); const templateId = form.find('#bulk-template-id').val(); // Collect variations data const variations = {}; form.find('.variation-row').each(function(index) { const row = $(this); const variationData = {}; row.find('input, textarea').each(function() { const field = $(this); const name = field.attr('name'); const value = field.val(); if (name && value) { const fieldName = name.match(/\[([^\]]+)\]$/)?.[1]; if (fieldName) { variationData[fieldName] = value; } } }); if (Object.keys(variationData).length > 0) { variations[index + 1] = variationData; } }); if (Object.keys(variations).length === 0) { this.showMessage('Please provide at least one event variation', 'error'); return; } // Show loading state const originalText = submitButton.text(); submitButton.text('Creating Events...').prop('disabled', true); // Start bulk operation $.ajax({ url: hvacBulkOperations.ajaxurl, method: 'POST', data: { action: 'hvac_start_bulk_operation', nonce: hvacBulkOperations.nonce, operation_type: 'bulk_create', template_id: templateId, variations: JSON.stringify(Object.values(variations)) }, success: (response) => { if (response.success) { // Close variations modal this.closeModal(); // Start tracking the operation this.trackOperation(response.data.operation_id); this.startProgressPolling(); // Show progress modal this.showProgressModal(response.data.operation_id, { title: 'Creating Events from Template', totalItems: response.data.total_items }); this.showMessage(response.data.message, 'success'); } else { this.showMessage(response.data?.message || 'Failed to start bulk operation', 'error'); } }, error: () => { this.showMessage('An unexpected error occurred', 'error'); }, complete: () => { submitButton.text(originalText).prop('disabled', false); } }); }, /** * Handle template application to multiple events */ handleTemplateApplicationBulk: function(event) { event.preventDefault(); const button = $(event.target); const templateId = button.data('template-id'); const selectedEvents = this.getSelectedEvents(); if (!templateId) { this.showMessage('No template selected for bulk application', 'error'); return; } if (selectedEvents.length === 0) { this.showMessage('Please select events to apply template to', 'error'); return; } if (!confirm(`Apply template to ${selectedEvents.length} selected events?`)) { return; } // Show loading state const originalText = button.text(); button.text('Applying Template...').prop('disabled', true); // Start template application $.ajax({ url: hvacBulkOperations.ajaxurl, method: 'POST', data: { action: 'hvac_start_bulk_operation', nonce: hvacBulkOperations.nonce, operation_type: 'template_apply', template_id: templateId, event_ids: JSON.stringify(selectedEvents), options: JSON.stringify({ update_content: true }) }, success: (response) => { if (response.success) { // Start tracking the operation this.trackOperation(response.data.operation_id); this.startProgressPolling(); // Show progress modal this.showProgressModal(response.data.operation_id, { title: 'Applying Template to Events', totalItems: response.data.total_items }); this.showMessage(response.data.message, 'success'); } else { this.showMessage(response.data?.message || 'Failed to start template application', 'error'); } }, error: () => { this.showMessage('An unexpected error occurred', 'error'); }, complete: () => { button.text(originalText).prop('disabled', false); } }); }, /** * Show progress modal for bulk operation */ showProgressModal: function(operationId, options = {}) { let modal = $('#hvac-bulk-progress-modal'); if (!modal.length) { this.createProgressModal(); modal = $('#hvac-bulk-progress-modal'); } // Set modal content modal.find('.progress-title').text(options.title || 'Processing Bulk Operation'); modal.find('.progress-operation-id').text(operationId); modal.find('.progress-total-items').text(options.totalItems || '...'); modal.find('.progress-processed-items').text('0'); modal.find('.progress-failed-items').text('0'); modal.find('.progress-percentage').text('0%'); modal.find('.progress-bar-fill').css('width', '0%'); modal.find('.progress-status').text('Starting...'); modal.find('.progress-results').empty().addClass('hidden'); // Store operation info modal.data('operation-id', operationId); // Show modal modal.removeClass('hidden'); }, /** * Create progress tracking modal */ createProgressModal: function() { const modalHtml = ` `; $('body').append(modalHtml); }, /** * Track bulk operation progress */ trackOperation: function(operationId) { this.activeOperations.set(operationId, { id: operationId, startTime: Date.now() }); this.saveOperationToStorage(operationId); }, /** * Start progress polling */ startProgressPolling: function() { if (this.progressPollingInterval) { return; // Already polling } this.progressPollingInterval = setInterval(() => { this.pollOperationProgress(); }, this.pollingFrequency); }, /** * Stop progress polling */ stopProgressPolling: function() { if (this.progressPollingInterval) { clearInterval(this.progressPollingInterval); this.progressPollingInterval = null; } }, /** * Poll operation progress for all active operations */ pollOperationProgress: function() { if (this.activeOperations.size === 0) { this.stopProgressPolling(); return; } this.activeOperations.forEach((operation, operationId) => { this.checkOperationProgress(operationId); }); }, /** * Check progress for specific operation */ checkOperationProgress: function(operationId) { $.ajax({ url: hvacBulkOperations.ajaxurl, method: 'GET', data: { action: 'hvac_get_bulk_progress', nonce: hvacBulkOperations.nonce, operation_id: operationId }, success: (response) => { if (response.success) { this.updateProgressDisplay(response.data); // Check if operation is complete if (['completed', 'failed', 'cancelled'].includes(response.data.status)) { this.completeOperation(operationId, response.data); } } }, error: () => { // Operation might not exist anymore, remove it this.completeOperation(operationId, { status: 'error' }); } }); }, /** * Update progress display in modal */ updateProgressDisplay: function(progressData) { const modal = $('#hvac-bulk-progress-modal'); if (!modal.is(':visible') || modal.data('operation-id') !== progressData.operation_id) { return; } modal.find('.progress-processed-items').text(progressData.processed_items); modal.find('.progress-failed-items').text(progressData.failed_items); modal.find('.progress-percentage').text(progressData.progress_percentage + '%'); modal.find('.progress-bar-fill').css('width', progressData.progress_percentage + '%'); // Update status const statusText = this.getStatusText(progressData.status, progressData); modal.find('.progress-status').text(statusText); // Show results if completed if (progressData.status === 'completed') { this.showOperationResults(modal, progressData); } }, /** * Complete operation tracking */ completeOperation: function(operationId, progressData) { this.activeOperations.delete(operationId); this.removeOperationFromStorage(operationId); // Update UI for completion if (progressData.status === 'completed') { const modal = $('#hvac-bulk-progress-modal'); modal.find('.hvac-cancel-operation-btn').addClass('hidden'); modal.find('.hvac-close-progress-modal').removeClass('hidden'); // Show completion message const totalItems = progressData.total_items || 0; const successItems = totalItems - (progressData.failed_items || 0); this.showMessage(`Operation completed! ${successItems} items processed successfully.`, 'success'); // Auto-close modal after 10 seconds setTimeout(() => { if (modal.is(':visible')) { this.closeProgressModal(); } }, 10000); } // Stop polling if no more active operations if (this.activeOperations.size === 0) { this.stopProgressPolling(); } }, /** * Show operation results in modal */ showOperationResults: function(modal, progressData) { const resultsContainer = modal.find('.progress-results'); const resultsContent = resultsContainer.find('.results-content'); let resultsHtml = ''; // Success results if (progressData.results && progressData.results.length > 0) { resultsHtml += '
'; resultsHtml += '
Successfully Created Events
'; resultsHtml += ''; resultsHtml += '
'; } // Error results if (progressData.errors && progressData.errors.length > 0) { resultsHtml += '
'; resultsHtml += '
Failed Items
'; resultsHtml += ''; resultsHtml += '
'; } resultsContent.html(resultsHtml); resultsContainer.removeClass('hidden'); }, /** * Handle operation cancellation */ handleCancelOperation: function(event) { event.preventDefault(); const modal = $('#hvac-bulk-progress-modal'); const operationId = modal.data('operation-id'); if (!operationId) { return; } if (!confirm('Are you sure you want to cancel this operation?')) { return; } const button = $(event.target); const originalText = button.text(); button.text('Cancelling...').prop('disabled', true); $.ajax({ url: hvacBulkOperations.ajaxurl, method: 'POST', data: { action: 'hvac_cancel_bulk_operation', nonce: hvacBulkOperations.nonce, operation_id: operationId }, success: (response) => { if (response.success) { this.showMessage('Operation cancelled successfully', 'success'); this.closeProgressModal(); } else { this.showMessage(response.data?.message || 'Failed to cancel operation', 'error'); } }, error: () => { this.showMessage('An unexpected error occurred', 'error'); }, complete: () => { button.text(originalText).prop('disabled', false); } }); }, /** * Event selection handling */ getSelectedEvents: function() { return $('.hvac-bulk-event-checkbox:checked').map(function() { return $(this).val(); }).get(); }, handleEventSelection: function() { this.updateBulkActionButtons(); }, handleSelectAll: function(event) { const isChecked = $(event.target).is(':checked'); $('.hvac-bulk-event-checkbox').prop('checked', isChecked); this.updateBulkActionButtons(); }, updateBulkActionButtons: function() { const selectedCount = this.getSelectedEvents().length; const bulkButtons = $('.hvac-bulk-action-btn'); if (selectedCount > 0) { bulkButtons.prop('disabled', false).find('.selected-count').text(selectedCount); } else { bulkButtons.prop('disabled', true).find('.selected-count').text('0'); } }, /** * Utility functions */ getStatusText: function(status, progressData) { switch (status) { case 'pending': return 'Operation queued...'; case 'running': return `Processing... (${progressData.processed_items}/${progressData.total_items})`; case 'completed': return 'Operation completed successfully'; case 'failed': return 'Operation failed'; case 'cancelled': return 'Operation cancelled'; default: return 'Unknown status'; } }, closeModal: function() { $('.hvac-modal').addClass('hidden'); }, closeProgressModal: function() { $('#hvac-bulk-progress-modal').addClass('hidden'); }, showMessage: function(message, type = 'info') { // Use the existing template message system if available if (window.HVACEventTemplates && window.HVACEventTemplates.showMessage) { window.HVACEventTemplates.showMessage(message, type); return; } // Fallback message display $('.hvac-message').remove(); const messageClass = 'hvac-message hvac-message-' + type; const messageHtml = '
' + this.escapeHtml(message) + '
'; $('body').prepend(messageHtml); setTimeout(function() { $('.hvac-message').fadeOut(function() { $(this).remove(); }); }, 5000); }, escapeHtml: function(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }, // Local storage helpers for operation persistence getSavedOperations: function() { try { const saved = localStorage.getItem('hvac_bulk_operations'); return saved ? JSON.parse(saved) : []; } catch (e) { return []; } }, saveOperationToStorage: function(operationId) { try { const operations = this.getSavedOperations(); if (!operations.includes(operationId)) { operations.push(operationId); localStorage.setItem('hvac_bulk_operations', JSON.stringify(operations)); } } catch (e) { // Storage not available, ignore } }, removeOperationFromStorage: function(operationId) { try { const operations = this.getSavedOperations(); const filtered = operations.filter(id => id !== operationId); localStorage.setItem('hvac_bulk_operations', JSON.stringify(filtered)); } catch (e) { // Storage not available, ignore } } }; // Initialize when document is ready $(document).ready(function() { HVACBulkOperations.init(); }); })(jQuery);