/** * Descomplicar® Crescimento Digital * https://descomplicar.pt */ /** * Desk-Moloni Queue Management JavaScript * Handles queue operations and real-time updates * * @package Desk-Moloni * @version 3.0.0 * @author Descomplicar Business Solutions */ $(document).ready(function() { 'use strict'; // Queue Manager object window.QueueManager = { config: { refreshInterval: 10000, maxRetryAttempts: 3, itemsPerPage: 50 }, state: { currentPage: 1, filters: {}, selectedTasks: [], sortField: 'scheduled_at', sortDirection: 'desc' }, timers: {}, init: function() { this.bindEvents(); this.initializeFilters(); this.loadQueue(); this.startAutoRefresh(); }, bindEvents: function() { // Refresh queue $('#refresh-queue').on('click', this.loadQueue.bind(this)); // Toggle processing $('#toggle-processing').on('click', this.toggleProcessing.bind(this)); // Apply filters $('#apply-filters').on('click', this.applyFilters.bind(this)); $('#clear-filters').on('click', this.clearFilters.bind(this)); // Filter changes $('#queue-filters select, #queue-filters input').on('change', this.handleFilterChange.bind(this)); // Task selection $(document).on('change', '#table-select-all', this.handleSelectAll.bind(this)); $(document).on('change', '.task-checkbox', this.handleTaskSelection.bind(this)); // Bulk actions $(document).on('click', '[data-action]', this.handleBulkAction.bind(this)); // Individual task actions $(document).on('click', '[data-task-action]', this.handleTaskAction.bind(this)); // Pagination $(document).on('click', '.pagination a', this.handlePagination.bind(this)); // Sort handlers $(document).on('click', '[data-sort]', this.handleSort.bind(this)); // Clear completed tasks $('#clear-completed').on('click', this.clearCompleted.bind(this)); // Add task form $('#add-task-form').on('submit', this.addTask.bind(this)); // Task details modal $(document).on('click', '[data-task-details]', this.showTaskDetails.bind(this)); }, initializeFilters: function() { // Set default date filters var today = new Date(); var weekAgo = new Date(); weekAgo.setDate(weekAgo.getDate() - 7); $('#filter-date-from').val(weekAgo.toISOString().split('T')[0]); $('#filter-date-to').val(today.toISOString().split('T')[0]); }, loadQueue: function() { var self = this; var params = $.extend({}, this.state.filters, { limit: this.config.itemsPerPage, offset: (this.state.currentPage - 1) * this.config.itemsPerPage, sort_field: this.state.sortField, sort_direction: this.state.sortDirection }); // Show loading state $('#queue-table tbody').html(' Loading queue...'); $.ajax({ url: admin_url + 'modules/desk_moloni/queue/get_queue_status', type: 'GET', data: params, dataType: 'json', success: function(response) { if (response.success) { self.renderQueue(response.data); self.updateSummary(response.data); } else { self.showError('Failed to load queue: ' + response.message); } }, error: function(xhr, status, error) { self.showError('Failed to load queue data'); $('#queue-table tbody').html('Failed to load queue data'); } }); }, renderQueue: function(data) { var tbody = $('#queue-table tbody'); tbody.empty(); if (!data.tasks || data.tasks.length === 0) { tbody.html('No tasks found'); return; } $.each(data.tasks, function(index, task) { var row = QueueManager.createTaskRow(task); tbody.append(row); }); // Update pagination this.updatePagination(data.pagination); // Update selection state this.updateSelectionControls(); }, createTaskRow: function(task) { var statusClass = this.getStatusClass(task.status); var priorityClass = this.getPriorityClass(task.priority); var priorityLabel = this.getPriorityLabel(task.priority); var actions = this.createTaskActions(task); var row = '' + '' + '#' + task.id + '' + '' + this.formatTaskType(task.task_type) + '' + '' + this.formatEntityInfo(task.entity_type, task.entity_id) + '' + '' + priorityLabel + '' + '' + task.status + '' + '' + task.attempts + '/' + task.max_attempts + '' + '' + this.formatDateTime(task.scheduled_at) + '' + '' + actions + '' + ''; return row; }, createTaskActions: function(task) { var actions = []; // Details button actions.push(''); // Retry button for failed tasks if (task.status === 'failed' || task.status === 'retry') { actions.push(''); } // Cancel button for pending/processing tasks if (task.status === 'pending' || task.status === 'processing') { actions.push(''); } // Delete button for completed/failed tasks if (task.status === 'completed' || task.status === 'failed') { actions.push(''); } return actions.join(' '); }, updateSummary: function(data) { if (data.summary) { $('#total-tasks').text(this.formatNumber(data.summary.total_tasks || 0)); $('#pending-tasks').text(this.formatNumber(data.summary.pending_tasks || 0)); $('#processing-tasks').text(this.formatNumber(data.summary.processing_tasks || 0)); $('#failed-tasks').text(this.formatNumber(data.summary.failed_tasks || 0)); } }, updatePagination: function(pagination) { var controls = $('#pagination-controls'); var info = $('#pagination-info'); controls.empty(); if (!pagination || pagination.total_pages <= 1) { info.text(''); return; } // Pagination info var start = ((pagination.current_page - 1) * pagination.per_page) + 1; var end = Math.min(start + pagination.per_page - 1, pagination.total_items); info.text('Showing ' + start + '-' + end + ' of ' + pagination.total_items + ' tasks'); // Previous button if (pagination.current_page > 1) { controls.append('
  • «
  • '); } // Page numbers var startPage = Math.max(1, pagination.current_page - 2); var endPage = Math.min(pagination.total_pages, startPage + 4); for (var i = startPage; i <= endPage; i++) { var activeClass = i === pagination.current_page ? ' class="active"' : ''; controls.append('' + i + ''); } // Next button if (pagination.current_page < pagination.total_pages) { controls.append('
  • »
  • '); } }, handleFilterChange: function(e) { var $input = $(e.target); var filterName = $input.attr('name'); var filterValue = $input.val(); this.state.filters[filterName] = filterValue; }, applyFilters: function(e) { e.preventDefault(); // Collect all filter values $('#queue-filters input, #queue-filters select').each(function() { var name = $(this).attr('name'); var value = $(this).val(); QueueManager.state.filters[name] = value; }); this.state.currentPage = 1; // Reset to first page this.loadQueue(); }, clearFilters: function(e) { e.preventDefault(); // Clear form and state $('#queue-filters')[0].reset(); this.state.filters = {}; this.state.currentPage = 1; this.loadQueue(); }, handleSelectAll: function(e) { var checked = $(e.target).is(':checked'); $('.task-checkbox').prop('checked', checked); this.updateSelectedTasks(); }, handleTaskSelection: function(e) { this.updateSelectedTasks(); // Update select all checkbox var totalCheckboxes = $('.task-checkbox').length; var checkedBoxes = $('.task-checkbox:checked').length; $('#table-select-all').prop('indeterminate', checkedBoxes > 0 && checkedBoxes < totalCheckboxes); $('#table-select-all').prop('checked', checkedBoxes === totalCheckboxes && totalCheckboxes > 0); }, updateSelectedTasks: function() { this.state.selectedTasks = $('.task-checkbox:checked').map(function() { return parseInt($(this).val()); }).get(); // Show/hide bulk actions if (this.state.selectedTasks.length > 0) { $('#bulk-actions').show(); } else { $('#bulk-actions').hide(); } }, updateSelectionControls: function() { // Clear selection when data refreshes this.state.selectedTasks = []; $('.task-checkbox').prop('checked', false); $('#table-select-all').prop('checked', false).prop('indeterminate', false); $('#bulk-actions').hide(); }, handleBulkAction: function(e) { e.preventDefault(); if (this.state.selectedTasks.length === 0) { this.showError('Please select tasks first'); return; } var action = $(e.target).closest('[data-action]').data('action'); var confirmMessage = this.getBulkActionConfirmMessage(action); if (!confirm(confirmMessage)) { return; } this.executeBulkAction(action, this.state.selectedTasks); }, executeBulkAction: function(action, taskIds) { var self = this; $.ajax({ url: admin_url + 'modules/desk_moloni/queue/bulk_operation', type: 'POST', data: { operation: action, task_ids: taskIds }, dataType: 'json', success: function(response) { if (response.success) { self.showSuccess(response.message); self.loadQueue(); } else { self.showError(response.message); } }, error: function() { self.showError('Bulk operation failed'); } }); }, handleTaskAction: function(e) { e.preventDefault(); var $btn = $(e.target).closest('[data-task-action]'); var action = $btn.data('task-action'); var taskId = $btn.data('task-id'); var confirmMessage = this.getTaskActionConfirmMessage(action); if (confirmMessage && !confirm(confirmMessage)) { return; } this.executeTaskAction(action, taskId); }, executeTaskAction: function(action, taskId) { var self = this; var url = admin_url + 'modules/desk_moloni/queue/' + action + '_task/' + taskId; $.ajax({ url: url, type: 'POST', dataType: 'json', success: function(response) { if (response.success) { self.showSuccess(response.message); self.loadQueue(); } else { self.showError(response.message); } }, error: function() { self.showError('Task action failed'); } }); }, handlePagination: function(e) { e.preventDefault(); var page = parseInt($(e.target).data('page')); if (page && page !== this.state.currentPage) { this.state.currentPage = page; this.loadQueue(); } }, handleSort: function(e) { e.preventDefault(); var field = $(e.target).data('sort'); if (this.state.sortField === field) { this.state.sortDirection = this.state.sortDirection === 'asc' ? 'desc' : 'asc'; } else { this.state.sortField = field; this.state.sortDirection = 'asc'; } this.loadQueue(); }, toggleProcessing: function(e) { e.preventDefault(); var self = this; var $btn = $(e.target); $.ajax({ url: admin_url + 'modules/desk_moloni/queue/toggle_processing', type: 'POST', dataType: 'json', success: function(response) { if (response.success) { self.showSuccess(response.message); self.updateToggleButton($btn, response.data.queue_processing_enabled); } else { self.showError(response.message); } }, error: function() { self.showError('Failed to toggle processing'); } }); }, updateToggleButton: function($btn, enabled) { var icon = enabled ? 'fa-pause' : 'fa-play'; var text = enabled ? 'Pause Processing' : 'Resume Processing'; var btnClass = enabled ? 'btn-warning' : 'btn-success'; $btn.find('#toggle-processing-icon').removeClass().addClass('fa ' + icon); $btn.find('#toggle-processing-text').text(text); $btn.removeClass('btn-warning btn-success').addClass(btnClass); }, clearCompleted: function(e) { e.preventDefault(); if (!confirm('Are you sure you want to clear all completed tasks older than 7 days?')) { return; } var self = this; $.ajax({ url: admin_url + 'modules/desk_moloni/queue/clear_completed', type: 'POST', data: { days_old: 7 }, dataType: 'json', success: function(response) { if (response.success) { self.showSuccess(response.message); self.loadQueue(); } else { self.showError(response.message); } }, error: function() { self.showError('Failed to clear completed tasks'); } }); }, addTask: function(e) { e.preventDefault(); var $form = $(e.target); var $submitBtn = $form.find('[type="submit"]'); // Validate JSON payload if provided var payload = $('#payload').val(); if (payload) { try { JSON.parse(payload); } catch (e) { this.showError('Invalid JSON payload'); return; } } this.showLoading($submitBtn); var self = this; $.ajax({ url: admin_url + 'modules/desk_moloni/queue/add_task', type: 'POST', data: $form.serialize(), dataType: 'json', success: function(response) { if (response.success) { self.showSuccess(response.message); $('#add-task-modal').modal('hide'); $form[0].reset(); self.loadQueue(); } else { self.showError(response.message); } }, error: function() { self.showError('Failed to add task'); }, complete: function() { self.hideLoading($submitBtn); } }); }, showTaskDetails: function(e) { e.preventDefault(); var taskId = $(e.target).closest('[data-task-details]').data('task-details'); $('#task-details-modal').data('task-id', taskId).modal('show'); }, startAutoRefresh: function() { var self = this; this.timers.autoRefresh = setInterval(function() { self.loadQueue(); }, this.config.refreshInterval); }, stopAutoRefresh: function() { if (this.timers.autoRefresh) { clearInterval(this.timers.autoRefresh); delete this.timers.autoRefresh; } }, // Helper methods getStatusClass: function(status) { switch (status) { case 'completed': return 'success'; case 'processing': return 'info'; case 'failed': return 'danger'; case 'retry': return 'warning'; case 'pending': return 'default'; default: return 'default'; } }, getPriorityClass: function(priority) { switch (parseInt(priority)) { case 1: return 'high'; case 5: return 'normal'; case 9: return 'low'; default: return 'normal'; } }, getPriorityLabel: function(priority) { switch (parseInt(priority)) { case 1: return 'High'; case 5: return 'Normal'; case 9: return 'Low'; default: return 'Normal'; } }, formatTaskType: function(taskType) { return taskType.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); }, formatEntityInfo: function(entityType, entityId) { var icon = this.getEntityIcon(entityType); return ' ' + this.formatTaskType(entityType) + ' #' + entityId; }, getEntityIcon: function(entityType) { switch (entityType) { case 'client': return 'fa-user'; case 'product': return 'fa-cube'; case 'invoice': return 'fa-file-text'; case 'estimate': return 'fa-file-o'; case 'credit_note': return 'fa-file'; default: return 'fa-question'; } }, getBulkActionConfirmMessage: function(action) { switch (action) { case 'retry': return 'Are you sure you want to retry the selected tasks?'; case 'cancel': return 'Are you sure you want to cancel the selected tasks?'; case 'delete': return 'Are you sure you want to delete the selected tasks? This action cannot be undone.'; default: return 'Are you sure you want to perform this action?'; } }, getTaskActionConfirmMessage: function(action) { switch (action) { case 'cancel': return 'Are you sure you want to cancel this task?'; case 'delete': return 'Are you sure you want to delete this task? This action cannot be undone.'; default: return null; // No confirmation needed } }, formatNumber: function(num) { return new Intl.NumberFormat().format(num); }, formatDateTime: function(dateString) { if (!dateString) return 'N/A'; var date = new Date(dateString); return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}); }, showLoading: function($element) { var originalText = $element.data('original-text') || $element.html(); $element.data('original-text', originalText); $element.prop('disabled', true) .html(' Loading...'); }, hideLoading: function($element) { var originalText = $element.data('original-text'); if (originalText) { $element.html(originalText); } $element.prop('disabled', false); }, showSuccess: function(message) { if (typeof window.DeskMoloniAdmin !== 'undefined') { window.DeskMoloniAdmin.showAlert('success', message); } else { alert(message); } }, showError: function(message) { if (typeof window.DeskMoloniAdmin !== 'undefined') { window.DeskMoloniAdmin.showAlert('danger', message); } else { alert(message); } } }; // Initialize queue manager window.QueueManager.init(); // Cleanup on page unload $(window).on('beforeunload', function() { window.QueueManager.stopAutoRefresh(); }); });