Feature 5: Implement Bulk Actions (Admin Only)

Add comprehensive bulk operations system for admins:

- Created BulkOperationsModel.php with operation tracking and processing
- Added bulk_operation.php API endpoint for bulk operations
- Created get_users.php API endpoint for user dropdown in bulk assign
- Updated DashboardView.php with checkboxes and bulk actions toolbar
- Added JavaScript functions for:
  - Select all/clear selection
  - Bulk close tickets
  - Bulk assign tickets
  - Bulk change priority
- Added comprehensive CSS for bulk actions toolbar and modals
- All bulk operations are admin-only (enforced server-side)
- Operations tracked in bulk_operations table with audit logging
- Supports bulk_close, bulk_assign, and bulk_priority operations

Admins can now select multiple tickets and perform batch operations, significantly improving workflow efficiency.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-01 19:06:33 -05:00
parent 353ce83a36
commit ac094c8706
6 changed files with 751 additions and 2 deletions

View File

@@ -913,3 +913,244 @@ function loadTemplate() {
alert('Error loading template: ' + error.message);
});
}
/**
* Bulk Actions Functions (Admin only)
*/
function toggleSelectAll() {
const selectAll = document.getElementById('selectAllCheckbox');
const checkboxes = document.querySelectorAll('.ticket-checkbox');
checkboxes.forEach(checkbox => {
checkbox.checked = selectAll.checked;
});
updateSelectionCount();
}
function updateSelectionCount() {
const checkboxes = document.querySelectorAll('.ticket-checkbox:checked');
const count = checkboxes.length;
const toolbar = document.querySelector('.bulk-actions-toolbar');
const countDisplay = document.getElementById('selected-count');
if (count > 0) {
toolbar.style.display = 'flex';
countDisplay.textContent = count;
} else {
toolbar.style.display = 'none';
}
}
function getSelectedTicketIds() {
const checkboxes = document.querySelectorAll('.ticket-checkbox:checked');
return Array.from(checkboxes).map(cb => parseInt(cb.value));
}
function clearSelection() {
document.querySelectorAll('.ticket-checkbox').forEach(cb => cb.checked = false);
const selectAll = document.getElementById('selectAllCheckbox');
if (selectAll) selectAll.checked = false;
updateSelectionCount();
}
function bulkClose() {
const ticketIds = getSelectedTicketIds();
if (ticketIds.length === 0) {
alert('No tickets selected');
return;
}
if (!confirm(`Close ${ticketIds.length} ticket(s)?`)) {
return;
}
fetch('/api/bulk_operation.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
operation_type: 'bulk_close',
ticket_ids: ticketIds
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert(`Bulk Close Complete:\n${data.processed} succeeded\n${data.failed} failed`);
window.location.reload();
} else {
alert('Error: ' + (data.error || 'Unknown error'));
}
})
.catch(error => {
console.error('Error performing bulk close:', error);
alert('Error performing bulk close: ' + error.message);
});
}
function showBulkAssignModal() {
const ticketIds = getSelectedTicketIds();
if (ticketIds.length === 0) {
alert('No tickets selected');
return;
}
// Create modal HTML
const modalHtml = `
<div class="modal-overlay" id="bulkAssignModal">
<div class="modal-content">
<h3>Assign ${ticketIds.length} Ticket(s)</h3>
<div class="modal-body">
<label for="bulkAssignUser">Assign to:</label>
<select id="bulkAssignUser" class="editable">
<option value="">Select User...</option>
<!-- Users will be loaded dynamically -->
</select>
</div>
<div class="modal-footer">
<button onclick="performBulkAssign()" class="btn btn-bulk">Assign</button>
<button onclick="closeBulkAssignModal()" class="btn btn-secondary">Cancel</button>
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
// Fetch users for the dropdown
fetch('/api/get_users.php')
.then(response => response.json())
.then(data => {
if (data.success && data.users) {
const select = document.getElementById('bulkAssignUser');
data.users.forEach(user => {
const option = document.createElement('option');
option.value = user.user_id;
option.textContent = user.display_name || user.username;
select.appendChild(option);
});
}
})
.catch(error => {
console.error('Error loading users:', error);
});
}
function closeBulkAssignModal() {
const modal = document.getElementById('bulkAssignModal');
if (modal) {
modal.remove();
}
}
function performBulkAssign() {
const userId = document.getElementById('bulkAssignUser').value;
const ticketIds = getSelectedTicketIds();
if (!userId) {
alert('Please select a user');
return;
}
fetch('/api/bulk_operation.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
operation_type: 'bulk_assign',
ticket_ids: ticketIds,
parameters: { assigned_to: parseInt(userId) }
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert(`Bulk Assign Complete:\n${data.processed} succeeded\n${data.failed} failed`);
closeBulkAssignModal();
window.location.reload();
} else {
alert('Error: ' + (data.error || 'Unknown error'));
}
})
.catch(error => {
console.error('Error performing bulk assign:', error);
alert('Error performing bulk assign: ' + error.message);
});
}
function showBulkPriorityModal() {
const ticketIds = getSelectedTicketIds();
if (ticketIds.length === 0) {
alert('No tickets selected');
return;
}
const modalHtml = `
<div class="modal-overlay" id="bulkPriorityModal">
<div class="modal-content">
<h3>Change Priority for ${ticketIds.length} Ticket(s)</h3>
<div class="modal-body">
<label for="bulkPriority">Priority:</label>
<select id="bulkPriority" class="editable">
<option value="">Select Priority...</option>
<option value="1">P1 - Critical Impact</option>
<option value="2">P2 - High Impact</option>
<option value="3">P3 - Medium Impact</option>
<option value="4">P4 - Low Impact</option>
<option value="5">P5 - Minimal Impact</option>
</select>
</div>
<div class="modal-footer">
<button onclick="performBulkPriority()" class="btn btn-bulk">Update</button>
<button onclick="closeBulkPriorityModal()" class="btn btn-secondary">Cancel</button>
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
}
function closeBulkPriorityModal() {
const modal = document.getElementById('bulkPriorityModal');
if (modal) {
modal.remove();
}
}
function performBulkPriority() {
const priority = document.getElementById('bulkPriority').value;
const ticketIds = getSelectedTicketIds();
if (!priority) {
alert('Please select a priority');
return;
}
fetch('/api/bulk_operation.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
operation_type: 'bulk_priority',
ticket_ids: ticketIds,
parameters: { priority: parseInt(priority) }
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert(`Bulk Priority Update Complete:\n${data.processed} succeeded\n${data.failed} failed`);
closeBulkPriorityModal();
window.location.reload();
} else {
alert('Error: ' + (data.error || 'Unknown error'));
}
})
.catch(error => {
console.error('Error performing bulk priority update:', error);
alert('Error performing bulk priority update: ' + error.message);
});
}