diff --git a/api/export_tickets.php b/api/export_tickets.php index 3cca27f..36b0d44 100644 --- a/api/export_tickets.php +++ b/api/export_tickets.php @@ -47,13 +47,31 @@ try { $type = isset($_GET['type']) ? $_GET['type'] : null; $search = isset($_GET['search']) ? trim($_GET['search']) : null; $format = isset($_GET['format']) ? $_GET['format'] : 'csv'; + $ticketIds = isset($_GET['ticket_ids']) ? $_GET['ticket_ids'] : null; // Initialize model $ticketModel = new TicketModel($conn); - // Get all tickets (no pagination for export) - $result = $ticketModel->getAllTickets(1, 10000, $status, 'created_at', 'desc', $category, $type, $search); - $tickets = $result['tickets']; + // Check if specific ticket IDs are provided + if ($ticketIds) { + // Parse and validate ticket IDs + $ticketIdArray = array_filter(array_map('trim', explode(',', $ticketIds))); + if (empty($ticketIdArray)) { + header('Content-Type: application/json'); + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'No valid ticket IDs provided']); + exit; + } + + // Get specific tickets by IDs + $tickets = $ticketModel->getTicketsByIds($ticketIdArray); + // Convert associative array to indexed array + $tickets = array_values($tickets); + } else { + // Get all tickets with filters (no pagination for export) + $result = $ticketModel->getAllTickets(1, 10000, $status, 'created_at', 'desc', $category, $type, $search); + $tickets = $result['tickets']; + } if ($format === 'csv') { // CSV Export diff --git a/api/get_users.php b/api/get_users.php index 83f367b..b651ec2 100644 --- a/api/get_users.php +++ b/api/get_users.php @@ -34,8 +34,8 @@ try { header('Content-Type: application/json'); - // Get all active users for mentions - $sql = "SELECT user_id, username, display_name FROM users WHERE is_active = 1 ORDER BY display_name, username"; + // Get all users for mentions/assignment + $sql = "SELECT user_id, username, display_name FROM users ORDER BY display_name, username"; $result = $conn->query($sql); $users = []; diff --git a/api/ticket_dependencies.php b/api/ticket_dependencies.php index 6bd6f51..b603d63 100644 --- a/api/ticket_dependencies.php +++ b/api/ticket_dependencies.php @@ -47,11 +47,16 @@ if ($conn->connect_error) { ResponseHelper::serverError('Database connection failed'); } -$dependencyModel = new DependencyModel($conn); -$auditLog = new AuditLogModel($conn); +try { + $dependencyModel = new DependencyModel($conn); + $auditLog = new AuditLogModel($conn); +} catch (Exception $e) { + ResponseHelper::serverError('Failed to initialize models: ' . $e->getMessage()); +} $method = $_SERVER['REQUEST_METHOD']; +try { switch ($method) { case 'GET': // Get dependencies for a ticket @@ -139,5 +144,8 @@ switch ($method) { default: ResponseHelper::error('Method not allowed', 405); } +} catch (Exception $e) { + ResponseHelper::serverError('An error occurred: ' . $e->getMessage()); +} $conn->close(); diff --git a/api/upload_attachment.php b/api/upload_attachment.php index 9a2b359..4c03561 100644 --- a/api/upload_attachment.php +++ b/api/upload_attachment.php @@ -31,8 +31,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { ResponseHelper::error('Ticket ID is required'); } - // Validate ticket ID format - if (!preg_match('/^[A-Z]{3}\d{6}$/', $ticketId)) { + // Validate ticket ID format (9-digit number) + if (!preg_match('/^\d{9}$/', $ticketId)) { ResponseHelper::error('Invalid ticket ID format'); } @@ -72,8 +72,8 @@ if (empty($ticketId)) { ResponseHelper::error('Ticket ID is required'); } -// Validate ticket ID format -if (!preg_match('/^[A-Z]{3}\d{6}$/', $ticketId)) { +// Validate ticket ID format (9-digit number) +if (!preg_match('/^\d{9}$/', $ticketId)) { ResponseHelper::error('Invalid ticket ID format'); } diff --git a/assets/css/dashboard.css b/assets/css/dashboard.css index 3d1bcd2..9bf7d28 100644 --- a/assets/css/dashboard.css +++ b/assets/css/dashboard.css @@ -3149,7 +3149,8 @@ tr:hover .quick-actions { box-shadow: 0 0 10px rgba(0, 255, 65, 0.3); } -.export-dropdown:hover .export-dropdown-content { +.export-dropdown:hover .export-dropdown-content, +.export-dropdown.open .export-dropdown-content { display: block; } diff --git a/assets/js/dashboard.js b/assets/js/dashboard.js index 5469563..97331ec 100644 --- a/assets/js/dashboard.js +++ b/assets/js/dashboard.js @@ -117,7 +117,10 @@ function initSettingsModal() { if (settingsIcon) { settingsIcon.addEventListener('click', function(e) { e.preventDefault(); - createSettingsModal(); + // openSettingsModal is defined in settings.js + if (typeof openSettingsModal === 'function') { + openSettingsModal(); + } }); } } @@ -482,6 +485,8 @@ function updateSelectionCount() { const count = checkboxes.length; const toolbar = document.querySelector('.bulk-actions-inline'); const countDisplay = document.getElementById('selected-count'); + const exportDropdown = document.getElementById('exportDropdown'); + const exportCount = document.getElementById('exportCount'); if (toolbar && countDisplay) { if (count > 0) { @@ -491,6 +496,16 @@ function updateSelectionCount() { toolbar.style.display = 'none'; } } + + // Show/hide export dropdown based on selection + if (exportDropdown) { + if (count > 0) { + exportDropdown.style.display = ''; + if (exportCount) exportCount.textContent = count; + } else { + exportDropdown.style.display = 'none'; + } + } } function getSelectedTicketIds() { @@ -1339,3 +1354,47 @@ function performQuickAssign(ticketId) { toast.error('Error updating assignment', 4000); }); } + +/** + * Toggle export dropdown menu + */ +function toggleExportMenu(event) { + event.stopPropagation(); + const dropdown = document.getElementById('exportDropdown'); + const content = document.getElementById('exportDropdownContent'); + if (dropdown && content) { + dropdown.classList.toggle('open'); + } +} + +// Close export dropdown when clicking outside +document.addEventListener('click', function(event) { + const dropdown = document.getElementById('exportDropdown'); + if (dropdown && !dropdown.contains(event.target)) { + dropdown.classList.remove('open'); + } +}); + +/** + * Export selected tickets to CSV or JSON + */ +function exportSelectedTickets(format) { + const ticketIds = getSelectedTicketIds(); + + if (ticketIds.length === 0) { + toast.warning('No tickets selected', 2000); + return; + } + + // Build URL with selected ticket IDs + const params = new URLSearchParams(); + params.set('format', format); + params.set('ticket_ids', ticketIds.join(',')); + + // Trigger download + window.location.href = '/api/export_tickets.php?' + params.toString(); + + // Close dropdown + const dropdown = document.getElementById('exportDropdown'); + if (dropdown) dropdown.classList.remove('open'); +} diff --git a/assets/js/ticket.js b/assets/js/ticket.js index a936c60..8640a5d 100644 --- a/assets/js/ticket.js +++ b/assets/js/ticket.js @@ -579,20 +579,39 @@ function loadDependencies() { const ticketId = window.ticketData.id; fetch(`/api/ticket_dependencies.php?ticket_id=${ticketId}`) - .then(response => response.json()) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + return response.json(); + }) .then(data => { if (data.success) { renderDependencies(data.dependencies); renderDependents(data.dependents); } else { console.error('Error loading dependencies:', data.error); + showDependencyError(data.error || 'Failed to load dependencies'); } }) .catch(error => { console.error('Error loading dependencies:', error); + showDependencyError('Failed to load dependencies. The feature may not be available.'); }); } +function showDependencyError(message) { + const dependenciesList = document.getElementById('dependenciesList'); + const dependentsList = document.getElementById('dependentsList'); + + if (dependenciesList) { + dependenciesList.innerHTML = `

${escapeHtml(message)}

`; + } + if (dependentsList) { + dependentsList.innerHTML = `

${escapeHtml(message)}

`; + } +} + function renderDependencies(dependencies) { const container = document.getElementById('dependenciesList'); if (!container) return; diff --git a/views/DashboardView.php b/views/DashboardView.php index 7cf6aed..69942ca 100644 --- a/views/DashboardView.php +++ b/views/DashboardView.php @@ -264,11 +264,11 @@
-
- -
- CSV - JSON + Total: