From 5120afddf527bd3483dfe82b471f059137822999 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Mon, 2 Dec 2024 21:21:10 -0500 Subject: [PATCH] Added discord webhooks, better filtering, and automated ticket creation --- assets/css/dashboard.css | 55 ++++++++++++++++++++++++++++++ assets/js/dashboard.js | 73 ++++++++++++++++++++++++++++++---------- create_ticket_api.php | 60 +++++++++++++++++++++++++++++---- dashboard.php | 21 ++++++++---- 4 files changed, 178 insertions(+), 31 deletions(-) diff --git a/assets/css/dashboard.css b/assets/css/dashboard.css index d68e1f0..a1e2de4 100644 --- a/assets/css/dashboard.css +++ b/assets/css/dashboard.css @@ -139,6 +139,61 @@ body { background: rgba(239, 68, 68, 0.1); } +.status-dropdown { + position: relative; + display: inline-block; + margin-right: 15px; +} + +.dropdown-header { + padding: 8px 15px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 4px; + cursor: pointer; + min-width: 120px; +} + +.dropdown-content { + display: none; + position: absolute; + top: 100%; + left: 0; + background: var(--bg-secondary); + min-width: 160px; + box-shadow: var(--shadow); + border-radius: 4px; + padding: 10px; + z-index: 100; + border: 1px solid var(--border-color); +} + +.status-dropdown.active .dropdown-content { + display: block; +} + +.dropdown-content label { + display: block; + padding: 8px; + cursor: pointer; + transition: background 0.2s; +} + +.dropdown-content label:hover { + background: var(--hover-bg); +} + +.dropdown-content .save-filter { + margin-top: 10px; + width: 100%; + padding: 8px; + background: #3b82f6; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} + /*UNCHECKED BELOW*/ body.menu-open { diff --git a/assets/js/dashboard.js b/assets/js/dashboard.js index b1ebc23..0372d52 100644 --- a/assets/js/dashboard.js +++ b/assets/js/dashboard.js @@ -291,25 +291,62 @@ function initSearch() { // Filter by status function initStatusFilter() { - const filter = document.createElement('select'); - filter.innerHTML = ` - - - - `; - filter.className = 'status-filter'; - filter.onchange = (e) => { - const status = e.target.value; - const rows = document.querySelectorAll('tbody tr'); - rows.forEach(row => { - if (!status || row.querySelector('.status-' + status)) { - row.style.display = ''; - } else { - row.style.display = 'none'; - } - }); + const filterContainer = document.createElement('div'); + filterContainer.className = 'status-filter-container'; + + // Create dropdown container + const dropdown = document.createElement('div'); + dropdown.className = 'status-dropdown'; + + // Create dropdown header + const dropdownHeader = document.createElement('div'); + dropdownHeader.className = 'dropdown-header'; + dropdownHeader.textContent = 'Status Filter'; + + // Create dropdown content + const dropdownContent = document.createElement('div'); + dropdownContent.className = 'dropdown-content'; + + const statuses = ['Open', 'Closed']; + statuses.forEach(status => { + const label = document.createElement('label'); + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.value = status; + checkbox.id = `status-${status.toLowerCase()}`; + + const urlParams = new URLSearchParams(window.location.search); + const currentStatuses = urlParams.get('status') ? urlParams.get('status').split(',') : []; + checkbox.checked = currentStatuses.includes(status); + + label.appendChild(checkbox); + label.appendChild(document.createTextNode(status)); + dropdownContent.appendChild(label); + }); + + const saveButton = document.createElement('button'); + saveButton.className = 'btn save-filter'; + saveButton.textContent = 'Apply Filter'; + + saveButton.onclick = () => { + const checkedBoxes = dropdownContent.querySelectorAll('input:checked'); + const selectedStatuses = Array.from(checkedBoxes).map(cb => cb.value); + localStorage.setItem('statusFilter', selectedStatuses.join(',')); + window.location.href = selectedStatuses.length ? `?status=${selectedStatuses.join(',')}` : '?'; + dropdown.classList.remove('active'); }; - document.querySelector('.table-controls .table-actions').prepend(filter); + + // Toggle dropdown on header click + dropdownHeader.onclick = () => { + dropdown.classList.toggle('active'); + }; + + dropdown.appendChild(dropdownHeader); + dropdown.appendChild(dropdownContent); + dropdownContent.appendChild(saveButton); + filterContainer.appendChild(dropdown); + + document.querySelector('.table-controls .table-actions').prepend(filterContainer); } function sortTable(table, column) { diff --git a/create_ticket_api.php b/create_ticket_api.php index d956e25..f845208 100644 --- a/create_ticket_api.php +++ b/create_ticket_api.php @@ -1,6 +1,11 @@ connect_error) { @@ -36,8 +41,17 @@ if ($conn->connect_error) { exit; } -// Get POST data -$data = json_decode(file_get_contents('php://input'), true); +// Force JSON content type for all incoming requests +header('Content-Type: application/json'); + +// Parse input regardless of content-type header +$rawInput = file_get_contents('php://input'); +$data = json_decode($rawInput, true); + +if (!$data) { + // Try parsing as URL-encoded data + parse_str($rawInput, $data); +} // Generate ticket ID (9-digit format with leading zeros) $ticket_id = sprintf('%09d', mt_rand(1, 999999999)); @@ -82,3 +96,37 @@ if ($stmt->execute()) { $stmt->close(); $conn->close(); + +// Discord webhook +$discord_webhook_url = $envVars['DISCORD_WEBHOOK_URL']; + +// Map priorities to Discord colors (decimal format) +$priorityColors = [ + "1" => 16736589, // --priority-1: #ff4d4d + "2" => 16753958, // --priority-2: #ffa726 + "3" => 4363509, // --priority-3: #42a5f5 + "4" => 6736490 // --priority-4: #66bb6a +]; + +$discord_data = [ + "content" => "", + "embeds" => [[ + "title" => "New Ticket Created: #" . $ticket_id, + "description" => $title, + "url" => "http://10.10.10.45/ticket.php?id=" . $ticket_id, + "color" => $priorityColors[$priority], + "fields" => [ + ["name" => "Priority", "value" => $priority, "inline" => true], + ["name" => "Category", "value" => $category, "inline" => true], + ["name" => "Type", "value" => $type, "inline" => true] + ] + ]] +]; + +$ch = curl_init($discord_webhook_url); +curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); +curl_setopt($ch, CURLOPT_POST, 1); +curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($discord_data)); +curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); +curl_exec($ch); +curl_close($ch); diff --git a/dashboard.php b/dashboard.php index 3103531..8ec4758 100644 --- a/dashboard.php +++ b/dashboard.php @@ -26,14 +26,21 @@ $defaultSortColumn = isset($_COOKIE['defaultSortColumn']) ? $_COOKIE['defaultSor $sortDirection = isset($_COOKIE['sortDirection']) ? $_COOKIE['sortDirection'] : 'desc'; $offset = ($page - 1) * $limit; -// Get total number of tickets -$totalTicketsQuery = "SELECT COUNT(*) as total FROM tickets"; +// Get total number of tickets based on current filter +$status = isset($_GET['status']) ? $_GET['status'] : 'Open'; +$statuses = explode(',', $status); +$whereClause = ""; +if (isset($_GET['status']) && !empty($_GET['status'])) { + $statuses = explode(',', $_GET['status']); + $whereClause = "WHERE status IN ('" . implode("','", $statuses) . "')"; +} +$totalTicketsQuery = "SELECT COUNT(*) as total FROM tickets $whereClause"; $totalTicketsResult = $conn->query($totalTicketsQuery); $totalTickets = $totalTicketsResult->fetch_assoc()['total']; $totalPages = ceil($totalTickets / $limit); -// Modify SQL to use these settings -$sql = "SELECT * FROM tickets ORDER BY $defaultSortColumn $sortDirection LIMIT $limit OFFSET $offset"; +// Modify SQL to use these settings and filter +$sql = "SELECT * FROM tickets $whereClause ORDER BY $defaultSortColumn $sortDirection LIMIT $limit OFFSET $offset"; $result = $conn->query($sql); ?> @@ -62,18 +69,18 @@ $result = $conn->query($sql); 1) { - echo ""; + echo ""; } // Page number buttons for ($i = 1; $i <= $totalPages; $i++) { $activeClass = ($i === $page) ? 'active' : ''; - echo ""; + echo ""; } // Next page button if ($page < $totalPages) { - echo ""; + echo ""; } ?>