🚀 PHASE 2A COMPLETE: Event Templates & Bulk Operations Infrastructure 📋 CORE IMPLEMENTATIONS: • HVAC_Event_Template_Manager - Complete CRUD operations with caching • HVAC_Event_Form_Builder - Extended form builder with template integration • HVAC_Bulk_Event_Manager - Bulk operations with background processing • Client-side template management with progress tracking • Comprehensive UI components with responsive design 🏗️ ARCHITECTURE HIGHLIGHTS: • Modern PHP 8+ patterns with strict typing • WordPress transient caching (15-minute TTL) • Security-first design with nonce validation • Performance optimization with lazy loading • Background job processing for bulk operations 📊 IMPLEMENTATION METRICS: • 4 new PHP classes (30K+ lines total) • 2 JavaScript modules (50K+ characters) • 2 CSS modules with responsive design • Comprehensive E2E test suite • Automated validation scripts 🔧 INTEGRATION POINTS: • Database table creation in activator • Plugin initialization integration • Asset loading with conditional enqueuing • AJAX endpoints with security validation • WordPress cron job scheduling 🧪 TESTING & VALIDATION: • Phase 2A comprehensive test suite (E2E) • Validation script with multiple checks • Documentation with implementation notes • Performance and security validation This completes Phase 2A deliverables with full template and bulk operations functionality. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
862 lines
No EOL
33 KiB
JavaScript
862 lines
No EOL
33 KiB
JavaScript
/**
|
|
* 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 = `
|
|
<div id="hvac-bulk-variations-modal" class="hvac-modal hidden">
|
|
<div class="hvac-modal-content hvac-bulk-modal-content">
|
|
<h3>Create Multiple Events from Template</h3>
|
|
|
|
<form id="hvac-bulk-variations-form">
|
|
<input type="hidden" id="bulk-template-id" name="template_id" value="">
|
|
|
|
<div class="form-section">
|
|
<p class="description">
|
|
Create multiple events by specifying variations for each event.
|
|
Common fields from the template will be applied to all events.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="bulk-variations-container">
|
|
<!-- Variation rows will be added here dynamically -->
|
|
</div>
|
|
|
|
<div class="bulk-actions">
|
|
<button type="button" class="button hvac-add-variation">
|
|
<span class="dashicons dashicons-plus-alt"></span> Add Event Variation
|
|
</button>
|
|
</div>
|
|
|
|
<div class="form-actions">
|
|
<button type="button" class="button hvac-close-modal">Cancel</button>
|
|
<button type="submit" class="button button-primary">Create Events</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
$('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 = `
|
|
<div class="variation-row" data-variation-index="${variationIndex}">
|
|
<div class="variation-header">
|
|
<h4>Event ${variationIndex}</h4>
|
|
<button type="button" class="button-link hvac-remove-variation" title="Remove this event">
|
|
<span class="dashicons dashicons-no-alt"></span>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="variation-fields">
|
|
<div class="field-row">
|
|
<label for="variation_${variationIndex}_title">Event Title *</label>
|
|
<input type="text" id="variation_${variationIndex}_title"
|
|
name="variations[${variationIndex}][event_title]"
|
|
class="regular-text" required>
|
|
</div>
|
|
|
|
<div class="field-row-group">
|
|
<div class="field-row half-width">
|
|
<label for="variation_${variationIndex}_start">Start Date *</label>
|
|
<input type="datetime-local" id="variation_${variationIndex}_start"
|
|
name="variations[${variationIndex}][event_start_date]"
|
|
class="regular-text" required>
|
|
</div>
|
|
|
|
<div class="field-row half-width">
|
|
<label for="variation_${variationIndex}_end">End Date *</label>
|
|
<input type="datetime-local" id="variation_${variationIndex}_end"
|
|
name="variations[${variationIndex}][event_end_date]"
|
|
class="regular-text" required>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field-row-group">
|
|
<div class="field-row half-width">
|
|
<label for="variation_${variationIndex}_capacity">Capacity</label>
|
|
<input type="number" id="variation_${variationIndex}_capacity"
|
|
name="variations[${variationIndex}][event_capacity]"
|
|
class="small-text" min="0">
|
|
</div>
|
|
|
|
<div class="field-row half-width">
|
|
<label for="variation_${variationIndex}_cost">Cost</label>
|
|
<input type="text" id="variation_${variationIndex}_cost"
|
|
name="variations[${variationIndex}][event_cost]"
|
|
class="small-text" placeholder="0.00">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field-row">
|
|
<label for="variation_${variationIndex}_description">Additional Description</label>
|
|
<textarea id="variation_${variationIndex}_description"
|
|
name="variations[${variationIndex}][event_description_extra]"
|
|
rows="3" class="large-text"></textarea>
|
|
<span class="description">This will be appended to the template description.</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
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 = `
|
|
<div id="hvac-bulk-progress-modal" class="hvac-modal hidden">
|
|
<div class="hvac-modal-content hvac-progress-modal-content">
|
|
<div class="progress-header">
|
|
<h3 class="progress-title">Processing Bulk Operation</h3>
|
|
<div class="progress-info">
|
|
<span class="progress-operation-id"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="progress-stats">
|
|
<div class="progress-stat">
|
|
<label>Total Items:</label>
|
|
<span class="progress-total-items">...</span>
|
|
</div>
|
|
<div class="progress-stat">
|
|
<label>Processed:</label>
|
|
<span class="progress-processed-items">0</span>
|
|
</div>
|
|
<div class="progress-stat">
|
|
<label>Failed:</label>
|
|
<span class="progress-failed-items">0</span>
|
|
</div>
|
|
<div class="progress-stat">
|
|
<label>Progress:</label>
|
|
<span class="progress-percentage">0%</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="progress-bar-container">
|
|
<div class="progress-bar">
|
|
<div class="progress-bar-fill"></div>
|
|
</div>
|
|
<div class="progress-status">Initializing...</div>
|
|
</div>
|
|
|
|
<div class="progress-results hidden">
|
|
<h4>Results</h4>
|
|
<div class="results-content"></div>
|
|
</div>
|
|
|
|
<div class="progress-actions">
|
|
<button type="button" class="button hvac-cancel-operation-btn">Cancel Operation</button>
|
|
<button type="button" class="button hvac-close-progress-modal hidden">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
$('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 += '<div class="results-section success-results">';
|
|
resultsHtml += '<h5>Successfully Created Events</h5>';
|
|
resultsHtml += '<ul>';
|
|
progressData.results.forEach(result => {
|
|
resultsHtml += `<li><strong>${this.escapeHtml(result.title)}</strong> (ID: ${result.event_id})</li>`;
|
|
});
|
|
resultsHtml += '</ul>';
|
|
resultsHtml += '</div>';
|
|
}
|
|
|
|
// Error results
|
|
if (progressData.errors && progressData.errors.length > 0) {
|
|
resultsHtml += '<div class="results-section error-results">';
|
|
resultsHtml += '<h5>Failed Items</h5>';
|
|
resultsHtml += '<ul>';
|
|
progressData.errors.forEach(error => {
|
|
resultsHtml += `<li><strong>Error:</strong> ${this.escapeHtml(error.error)}</li>`;
|
|
});
|
|
resultsHtml += '</ul>';
|
|
resultsHtml += '</div>';
|
|
}
|
|
|
|
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 = '<div class="' + messageClass + '">' + this.escapeHtml(message) + '</div>';
|
|
|
|
$('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); |