diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/assets/css/dashboard.css b/assets/css/dashboard.css new file mode 100644 index 0000000..d68e1f0 --- /dev/null +++ b/assets/css/dashboard.css @@ -0,0 +1,553 @@ +/* Variables */ +:root { + --bg-primary: #f5f7fa; + --bg-secondary: #ffffff; + --text-primary: #2c3e50; + --text-secondary: #4a5568; + --border-color: #eee; + --shadow: 0 2px 4px rgba(0,0,0,0.1); + --hover-bg: #f8f9fa; + --priority-1: #ff4d4d; + --priority-2: #ffa726; + --priority-3: #42a5f5; + --priority-4: #66bb6a; + + /* Spacing */ + --spacing-xs: 0.5rem; + --spacing-sm: 1rem; + --spacing-md: 1.5rem; + --spacing-lg: 2rem; + + /* Transitions */ + --transition-default: all 0.3s ease; +} + +/* Dark theme */ +[data-theme="dark"] { + --bg-primary: #1a202c; + --bg-secondary: #2d3748; + --text-primary: #f7fafc; + --text-secondary: #e2e8f0; + --border-color: #4a5568; + --shadow: 0 2px 4px rgba(0,0,0,0.3); + --hover-bg: #374151; + --priority-1: #7f1d1d; + --priority-2: #854d0e; + --priority-3: #075985; + --priority-4: #166534; +} + +/* Base Elements */ +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + margin: 0; + padding: var(--spacing-md); + background-color: var(--bg-primary); + color: var(--text-primary); + transition: var(--transition-default); +} + +/* Reusable Components */ +.card-base { + background: var(--bg-secondary); + border-radius: 12px; + box-shadow: var(--shadow); + padding: var(--spacing-md); +} + +.btn-base { + padding: var(--spacing-sm) var(--spacing-md); + border-radius: 6px; + border: none; + cursor: pointer; + transition: var(--transition-default); +} + +.btn-primary { + background: #3b82f6; + color: white; +} + +/* Layout Components */ +.dashboard-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--spacing-md); + margin-left: 3.75rem; +} + +.ticket-container { + max-width: 800px; + margin: var(--spacing-lg) auto; + border-left: 6px solid; + transition: var(--transition-default); +} + +.flex-row { + display: flex; + gap: var(--spacing-sm); +} + +.flex-between { + justify-content: space-between; + align-items: center; +} + +/* Table Styles */ +.table-base { + width: 100%; + border-collapse: separate; + border-spacing: 0; + overflow: hidden; +} + +.table-cell { + padding: var(--spacing-md); + text-align: left; + border-bottom: 1px solid var(--border-color); +} + +/* Priority Styles */ +.priority-indicator { + font-weight: bold; + font-family: 'Courier New', monospace; + padding: var(--spacing-xs) var(--spacing-sm); + border-radius: 4px; + display: inline-block; +} + +.priority-1 { color: var(--priority-1); } +.priority-2 { color: var(--priority-2); } +.priority-3 { color: var(--priority-3); } +.priority-4 { color: var(--priority-4); } + +/* Status Styles */ +.status-base { + font-weight: bold; + padding: var(--spacing-xs) var(--spacing-sm); + border-radius: 4px; +} + +.status-Open { + color: #10b981; + background: rgba(16, 185, 129, 0.1); +} + +.status-Closed { + color: #ef4444; + background: rgba(239, 68, 68, 0.1); +} + +/*UNCHECKED BELOW*/ + +body.menu-open { + padding-left: 260px; +} + +.create-ticket { + background: #3b82f6; + color: white; + padding: 0.625rem 1.25rem; + border-radius: 0.375rem; + border: none; + cursor: pointer; + font-weight: 500; + transition: background-color 0.3s ease; + margin-right: 3.75rem; +} + +.create-ticket:hover { + background: #2563eb; +} + +h1 { + color: var(--text-primary); + margin: 0; +} + +table { + width: 100%; + border-collapse: separate; + border-spacing: 0; + background: var(--bg-secondary); + border-radius: 12px; + box-shadow: var(--shadow); + overflow: hidden; +} + +th, td { + padding: 16px; + text-align: left; + border-bottom: 1px solid var(--border-color); + color: var(--text-primary); +} + +th { + background-color: var(--bg-secondary); + font-weight: 600; + text-transform: uppercase; + font-size: 0.9em; + letter-spacing: 0.05em; +} + +tr:hover { + background-color: var(--hover-bg); +} + +tbody tr td:first-child { + border-left: 6px solid; +} + +tbody tr.priority-1 td:first-child { border-left-color: var(--priority-1); } +tbody tr.priority-2 td:first-child { border-left-color: var(--priority-2); } +tbody tr.priority-3 td:first-child { border-left-color: var(--priority-3); } +tbody tr.priority-4 td:first-child { border-left-color: var(--priority-4); } + +/* Priority number styling */ +td:nth-child(2) { + text-align: center; +} + +td:nth-child(2) span { + font-weight: bold; + font-family: 'Courier New', monospace; + padding: 4px 8px; + border-radius: 4px; + display: inline-block; + background: var(--hover-bg); +} + +.priority-1 td:nth-child(2) { color: var(--priority-1); } +.priority-2 td:nth-child(2) { color: var(--priority-2); } +.priority-3 td:nth-child(2) { color: var(--priority-3); } +.priority-4 td:nth-child(2) { color: var(--priority-4); } + +.search-box { + padding: 0.5rem 0.75rem; + border: 1px solid var(--border-color); + border-radius: 0.375rem; + background: var(--bg-secondary); + color: var(--text-primary); + margin-left: 1.25rem; + width: 40%; +} + +.status-filter { + padding: 0.5rem 0.75rem; + border: 1px solid var(--border-color); + border-radius: 0.375rem; + background: var(--bg-secondary); + color: var(--text-primary); + cursor: pointer; + min-width: 120px; + margin-right: 1rem; +} + +.search-box:focus, +.status-filter:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1); +} + +.theme-toggle { + position: absolute; + top: 20px; + right: 20px; + z-index: 100; + padding: 12px; + border-radius: 50%; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + cursor: pointer; + box-shadow: var(--shadow); + font-size: 1.2em; + transition: transform 0.3s ease; +} + +.theme-toggle:hover { + transform: scale(1.1); +} + +.table-controls { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; + padding: 10px; + background: var(--bg-secondary); + border-radius: 8px; + box-shadow: var(--shadow); +} + +.ticket-count { + font-weight: 500; + color: var(--text-secondary); +} + +.table-actions { + display: flex; + gap: 15px; + align-items: center; +} +.pagination { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.pagination button { + padding: 0.5rem 0.75rem; + border: 1px solid var(--border-color); + background: var(--bg-secondary); + color: var(--text-primary); + border-radius: 0.375rem; + cursor: pointer; + transition: all 0.2s ease; +} + +.pagination button:hover { + background: var(--hover-bg); +} + +.pagination button.active { + background: #3b82f6; + color: white; + border-color: #3b82f6; +} + +.settings-icon { + cursor: pointer; + padding: 8px; + border-radius: 4px; + transition: background-color 0.2s; +} + +.settings-icon:hover { + background: var(--hover-bg); +} + +/* Settings Modal Styles */ +.settings-modal-backdrop { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; +} + +.settings-modal { + background: var(--bg-secondary); + border-radius: 12px; + width: 500px; + max-width: 90%; + box-shadow: var(--shadow); + padding: 20px; +} + +.settings-modal-header { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid var(--border-color); + padding-bottom: 15px; + margin-bottom: 15px; +} + +.settings-modal-header h2 { + margin: 0; + color: var(--text-primary); +} + +.close-modal { + background: none; + border: none; + font-size: 24px; + cursor: pointer; + color: var(--text-secondary); +} + +.setting-group { + margin-bottom: 15px; +} + +.setting-group label { + display: block; + margin-bottom: 8px; + color: var(--text-primary); +} + +.setting-group select { + width: 100%; + padding: 10px; + border: 1px solid var(--border-color); + border-radius: 6px; + background: var(--bg-primary); + color: var(--text-primary); +} + +.settings-modal-footer { + display: flex; + justify-content: flex-end; + gap: 10px; + border-top: 1px solid var(--border-color); + padding-top: 15px; +} + +.save-settings, .cancel-settings { + padding: 10px 20px; + border-radius: 6px; + border: none; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.save-settings { + background: #3b82f6; + color: white; +} + +.cancel-settings { + background: var(--hover-bg); + color: var(--text-primary); +} + +/* Sorting indicator styles */ +th { + position: relative; /* Ensure proper positioning of arrows */ + cursor: pointer; /* Show it's clickable */ +} + +th::after { + content: ''; + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + opacity: 0.5; /* Make arrows less prominent when not active */ +} + +th.sort-asc::after { + border-bottom: 7px solid var(--text-primary); + opacity: 1; +} + +th.sort-desc::after { + border-top: 7px solid var(--text-primary); + opacity: 1; +} +/* Column toggle styles */ +.column-toggles { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 10px; +} + +.column-toggles label { + display: flex; + align-items: center; + gap: 8px; +} + +.hamburger-menu { + position: absolute; + top: 20px; + left: 20px; + z-index: 100; +} + +.hamburger-icon { + cursor: pointer; + font-size: 24px; + background: var(--bg-secondary); + padding: 10px; + border-radius: 4px; + box-shadow: var(--shadow); +} + +.hamburger-content { + position: fixed; + top: 0; + left: -250px; + width: 200px; + height: 100%; + background: var(--bg-secondary); + box-shadow: 2px 0 5px rgba(0,0,0,0.1); + transition: left 0.3s ease, margin-left 0.3s ease; + padding: 40px 20px 20px; + overflow-y: auto; + z-index: 99; +} + +.hamburger-content.open { + left: 0; +} + +.close-hamburger { + position: absolute; + top: 10px; + right: 10px; + cursor: pointer; + font-size: 24px; + background: var(--bg-secondary); + padding: 10px; + border-radius: 4px; + box-shadow: var(--shadow); +} + +.filter-section { + margin-bottom: 20px; +} + +.filter-section label { + display: block; + margin-bottom: 10px; +} + +.filter-actions { + display: flex; + gap: 10px; +} + +.filter-actions button { + flex: 1; + padding: 10px; + border: none; + border-radius: 4px; + cursor: pointer; +} + +#apply-filters { + background: #3b82f6; + color: white; +} + +#clear-filters { + background: var(--hover-bg); + color: var(--text-primary); +} + +.ticket-link { + font-family: 'Courier New', monospace; + font-weight: bold; + color: var(--text-primary) !important; + text-decoration: none; + background: var(--hover-bg); + padding: 4px 8px; + border-radius: 4px; + display: inline-block; +} + +.ticket-link:hover { + background: var(--border-color); +} diff --git a/assets/css/ticket.css b/assets/css/ticket.css new file mode 100644 index 0000000..b980f8e --- /dev/null +++ b/assets/css/ticket.css @@ -0,0 +1,413 @@ +/* Base Layout Components */ +.ticket-container { + max-width: 800px; + margin: 40px auto; + padding: 20px; + background: var(--bg-secondary); + border-radius: 12px; + box-shadow: var(--shadow); + border-left: 6px solid; + transition: border-color 0.3s ease; +} + +.full-width { + grid-column: 1 / -1; +} +/* Header Components */ +.ticket-header { + display: flex; + flex-direction: column; + margin-bottom: 30px; +} + +.ticket-subheader { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 10px; +} + +.header-controls { + display: flex; + gap: 15px; + align-items: center; +} + +.ticket-id { + font-family: 'Courier New', monospace; + margin-right: 20px; +} + +h1 { + margin: 0; + padding: 0; + width: 100%; + display: block; +} + +/* Title Input Styles */ +.title-input { + font-size: 1em; + font-weight: bold; + width: 100%; + border: 2px solid transparent; + border-radius: 4px; + padding: 4px; + margin: -4px; + word-break: break-word; + white-space: normal; + display: block; +}.title-input:not(:disabled) { + border-color: var(--border-color); + background: var(--bg-primary); +} + +.title-input:not(:disabled):hover { + border-color: #3b82f6; +} + +.title-input:disabled { + color: var(--text-primary); + border: none; + background: transparent; +} + +/* Form Elements */ +.detail-group { + margin-bottom: 20px; +} + +.detail-group label { + display: block; + margin-bottom: 8px; + color: var(--text-secondary); + font-weight: 500; +} + +.editable { + padding: 10px; + border: 1px solid var(--border-color); + border-radius: 6px; + background: var(--bg-primary); + color: var(--text-primary); +} + +input.editable { + width: calc(100% - 20px); + box-sizing: border-box; +} + +textarea.editable { + width: calc(100% - 20px); + min-height: 150px; + resize: vertical; + box-sizing: border-box; +} + +.editable:disabled { + background: var(--bg-secondary); + cursor: default; + border-color: transparent; +} + +/* Button Styles */ +.btn { + padding: 10px 20px; + border: none; + border-radius: 6px; + cursor: pointer; + font-weight: 500; + background: var(--bg-primary); + color: var(--text-primary); +} + +.btn.primary { + background: #3b82f6; + color: white; +} + +.btn.active { + background: #3b82f6; + color: white; +} + +/* Status and Priority Styles */ +.status-priority-row { + display: flex; + gap: 20px; +} + +.detail-half { + flex: 1; +} + +.status-priority-group { + display: flex; + gap: 10px; + align-items: center; + margin-right: 15px; +} + +.priority-indicator { + padding: 4px 8px; + border-radius: 4px; + font-weight: bold; +} + +/* Priority Select Styles */ +select[data-field="priority"] { + border-left: 4px solid; +} + +select[data-field="priority"] option { + padding: 10px; +} + +select[data-field="priority"] option[value="1"] { + background-color: var(--priority-1); +} +select[data-field="priority"] option[value="2"] { + background-color: var(--priority-2); +} +select[data-field="priority"] option[value="3"] { + background-color: var(--priority-3); +} +select[data-field="priority"] option[value="4"] { + background-color: var(--priority-4); +} + +select[data-field="priority"][value="1"] { + border-left-color: var(--priority-1); +} +select[data-field="priority"][value="2"] { + border-left-color: var(--priority-2); +} +select[data-field="priority"][value="3"] { + border-left-color: var(--priority-3); +} +select[data-field="priority"][value="4"] { + border-left-color: var(--priority-4); +} + +select[data-field="priority"] option[value="1"]:hover { + background-color: #ffc9c9; + color: var(--text-primary); +} + +select[data-field="priority"] option[value="2"]:hover { + background-color: #ffe0b2; + color: var(--text-primary); +} + +select[data-field="priority"] option[value="3"]:hover { + background-color: #bbdefb; + color: var(--text-primary); +} + +select[data-field="priority"] option[value="4"]:hover { + background-color: #c8e6c9; + color: var(--text-primary); +} + +[data-priority="1"] { border-color: var(--priority-1); } +[data-priority="2"] { border-color: var(--priority-2); } +[data-priority="3"] { border-color: var(--priority-3); } +[data-priority="4"] { border-color: var(--priority-4); } + +/* Comments Section */ +.comments-section { + margin-top: 30px; + padding-top: 20px; + border-top: 1px solid var(--border-color); +} + +.comment-form { + margin-bottom: 20px; +} + +.comment-form textarea { + width: calc(100% - 20px); + min-height: 80px; + margin-bottom: 10px; + padding: 10px; + border-radius: 6px; + border: 1px solid var(--border-color); + background: var(--bg-primary); + color: var(--text-primary); +} + +.comment { + background: var(--bg-primary); + padding: 15px; + border-radius: 6px; + margin-bottom: 10px; +} + +.comment-header { + display: flex; + justify-content: space-between; + margin-bottom: 8px; + font-size: 0.9em; +} + +.comment-user { + font-weight: bold; + color: var(--text-primary); +} + +.comment-date { + color: var(--text-secondary); +} + +.comment-text { + color: var(--text-primary); + line-height: 1.4; +} + +.comment-controls { + display: flex; + justify-content: space-between; + align-items: center; + margin: 10px 0; +} + +/* Comment Tabs */ +.ticket-tabs { + display: flex; + gap: 10px; + margin: 20px 0; +} + +.tab-btn { + padding: 10px 20px; + border: 1px solid var(--border-color); + background: var(--bg-secondary); + border-radius: 6px; + cursor: pointer; + font-weight: 500; +} + +.tab-btn.active { + background: #3b82f6; + color: white; + border-color: #3b82f6; +} + +.tab-content { + display: none; +} + +.tab-content.active { + display: block; +} + +/* Markdown Styles */ +.markdown-preview { + min-height: 100px; + padding: 10px; + border: 1px solid var(--border-color); + border-radius: 6px; + background: var(--bg-primary); +} + +.markdown-preview h1, +.markdown-preview h2, +.markdown-preview h3 { + margin-top: 0; +} + +.markdown-preview code { + background: var(--bg-secondary); + padding: 2px 4px; + border-radius: 4px; +} + +.markdown-preview pre { + background: var(--bg-secondary); + padding: 10px; + border-radius: 6px; + overflow-x: auto; +} + +.markdown-toggles { + display: flex; + gap: 20px; +} + +/* Toggle Switch */ +.switch { + position: relative; + display: inline-block; + width: 50px; + height: 24px; +} + +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: var(--bg-secondary); + transition: .4s; +} + +.slider:before { + position: absolute; + content: ""; + height: 16px; + width: 16px; + left: 4px; + bottom: 4px; + background-color: white; + transition: .4s; +} + +.slider.round { + border-radius: 24px; +} + +.slider.round:before { + border-radius: 50%; +} + +input:checked + .slider { + background-color: #3b82f6; +} + +input:checked + .slider:before { + transform: translateX(26px); +} + +.switch input:disabled + .slider { + opacity: 0.5; + cursor: not-allowed; +} + +/* Footer */ +.ticket-footer { + margin-top: 30px; + padding-top: 20px; + border-top: 1px solid var(--border-color); +} + +.back-btn { + background: var(--bg-secondary); + color: var(--text-primary); + padding: 10px 20px; + border-radius: 6px; + border: 1px solid var(--border-color); + cursor: pointer; + transition: background-color 0.3s ease; +} + +.back-btn:hover { + background: var(--border-color); +} \ No newline at end of file diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000..2a27167 Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/js/dashboard.js b/assets/js/dashboard.js new file mode 100644 index 0000000..b1ebc23 --- /dev/null +++ b/assets/js/dashboard.js @@ -0,0 +1,487 @@ +document.addEventListener('DOMContentLoaded', function() { + // Only initialize filters if we're on the dashboard + if (document.querySelector('table')) { + initSearch(); + initStatusFilter(); + } + + // Keep theme toggle for all pages + initThemeToggle(); + + // Load saved theme preference + const savedTheme = localStorage.getItem('theme') || 'light'; + document.documentElement.setAttribute('data-theme', savedTheme); + + // Add sorting functionality + const tableHeaders = document.querySelectorAll('th'); + tableHeaders.forEach(header => { + header.addEventListener('click', () => { + const table = header.closest('table'); + const index = Array.from(header.parentElement.children).indexOf(header); + sortTable(table, index); + }); + }); + + // Add settings modal functionality + const settingsIcon = document.querySelector('.settings-icon'); + if (settingsIcon) { + settingsIcon.addEventListener('click', function(e) { + e.preventDefault(); + createSettingsModal(); + }); + } +}); + +function sortTable(table, column) { + // Remove existing sort indicators from all headers + const headers = table.querySelectorAll('th'); + headers.forEach(header => { + header.classList.remove('sort-asc', 'sort-desc'); + }); + + const rows = Array.from(table.querySelectorAll('tbody tr')); + + // Determine current sort direction + const currentDirection = table.dataset.sortColumn === column + ? (table.dataset.sortDirection === 'asc' ? 'desc' : 'asc') + : 'asc'; + + // Store current sorting column and direction + table.dataset.sortColumn = column; + table.dataset.sortDirection = currentDirection; + + rows.sort((a, b) => { + const aValue = a.children[column].textContent.trim(); + const bValue = b.children[column].textContent.trim(); + + // Try numeric sorting first, fallback to string comparison + const numA = parseFloat(aValue); + const numB = parseFloat(bValue); + + if (!isNaN(numA) && !isNaN(numB)) { + return currentDirection === 'asc' ? numA - numB : numB - numA; + } + + // String comparison + return currentDirection === 'asc' + ? aValue.localeCompare(bValue) + : bValue.localeCompare(aValue); + }); + + // Add sort indicator to the current header + const currentHeader = headers[column]; + currentHeader.classList.add(currentDirection === 'asc' ? 'sort-asc' : 'sort-desc'); + + // Reorder rows in the tbody + const tbody = table.querySelector('tbody'); + rows.forEach(row => tbody.appendChild(row)); +} + +// Add this to the DOMContentLoaded event listener to persist sorting on page load +document.addEventListener('DOMContentLoaded', function() { + const table = document.querySelector('table'); + if (table) { + const savedSortColumn = localStorage.getItem('sortColumn'); + const savedSortDirection = localStorage.getItem('sortDirection'); + + if (savedSortColumn !== null && savedSortDirection !== null) { + const headers = table.querySelectorAll('th'); + const columnIndex = Array.from(headers).findIndex(header => + header.textContent.toLowerCase().replace(' ', '_') === savedSortColumn + ); + + if (columnIndex !== -1) { + table.dataset.sortColumn = columnIndex; + table.dataset.sortDirection = savedSortDirection; + + const header = headers[columnIndex]; + header.classList.add(savedSortDirection === 'asc' ? 'sort-asc' : 'sort-desc'); + } + } + } +}); + +// Modify the existing event listeners for table headers +document.addEventListener('DOMContentLoaded', function() { + const tableHeaders = document.querySelectorAll('th'); + tableHeaders.forEach((header, index) => { + header.addEventListener('click', () => { + const table = header.closest('table'); + sortTable(table, index); + + // Save sorting preferences + const columnName = header.textContent.toLowerCase().replace(' ', '_'); + localStorage.setItem('sortColumn', columnName); + localStorage.setItem('sortDirection', table.dataset.sortDirection); + }); + }); +}); +function createSettingsModal() { + // Create modal backdrop + const backdrop = document.createElement('div'); + backdrop.className = 'settings-modal-backdrop'; + backdrop.innerHTML = ` +
+
+

Dashboard Settings

+ +
+
+
+

Toggle Columns

+
+ + + + + + + + +
+
+
+

Rows per Page

+ +
+
+ +
+ `; + + // Add to body + document.body.appendChild(backdrop); + + // Load saved column visibility settings + const savedColumnSettings = JSON.parse(localStorage.getItem('columnVisibility') || '{}'); + const checkboxes = backdrop.querySelectorAll('.column-toggles input'); + checkboxes.forEach(checkbox => { + checkbox.checked = savedColumnSettings[checkbox.value] !== false; + }); + + // Load saved rows per page setting + const savedRowsPerPage = localStorage.getItem('ticketsPerPage') || '5'; + const rowsPerPageSelect = backdrop.querySelector('#rows-per-page'); + rowsPerPageSelect.value = savedRowsPerPage; + + // Close modal events + backdrop.querySelector('.close-modal').addEventListener('click', closeSettingsModal); + backdrop.querySelector('.cancel-settings').addEventListener('click', closeSettingsModal); + backdrop.querySelector('.save-settings').addEventListener('click', saveSettings); + + // Close modal on backdrop click + backdrop.addEventListener('click', (e) => { + if (e.target === backdrop) { + closeSettingsModal(); + } + }); +} + +function closeSettingsModal() { + const backdrop = document.querySelector('.settings-modal-backdrop'); + if (backdrop) { + backdrop.remove(); + } +} + +function saveSettings() { + // Save column visibility + const checkboxes = document.querySelectorAll('.column-toggles input'); + const columnVisibility = {}; + + checkboxes.forEach(checkbox => { + columnVisibility[checkbox.value] = checkbox.checked; + }); + localStorage.setItem('columnVisibility', JSON.stringify(columnVisibility)); + + // Save rows per page + const rowsPerPage = document.querySelector('#rows-per-page').value; + localStorage.setItem('ticketsPerPage', rowsPerPage); + + // Set cookie for PHP to read + document.cookie = `ticketsPerPage=${rowsPerPage}; path=/`; + + // Apply column visibility + applyColumnVisibility(); + + // Reload page to apply pagination changes + window.location.reload(); + + // Close modal + closeSettingsModal(); +} + +function applyColumnVisibility() { + const savedColumnSettings = JSON.parse(localStorage.getItem('columnVisibility') || '{}'); + const table = document.querySelector('table'); + + if (table) { + const headers = table.querySelectorAll('th'); + const rows = table.querySelectorAll('tbody tr'); + + headers.forEach((header, index) => { + const columnValue = header.textContent.toLowerCase().replace(' ', '_'); + const isVisible = savedColumnSettings[columnValue] !== false; + + header.style.display = isVisible ? '' : 'none'; + + rows.forEach(row => { + row.children[index].style.display = isVisible ? '' : 'none'; + }); + }); + } +} + +// Apply column visibility on page load +document.addEventListener('DOMContentLoaded', applyColumnVisibility); +// Dark mode toggle +function initThemeToggle() { + const toggle = document.createElement('button'); + toggle.className = 'theme-toggle'; + toggle.innerHTML = '🌓'; + toggle.onclick = () => { + document.documentElement.setAttribute('data-theme', + document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark' + ); + localStorage.setItem('theme', document.documentElement.getAttribute('data-theme')); + }; + document.body.appendChild(toggle); +} + +// Search functionality +function initSearch() { + const searchBox = document.createElement('input'); + searchBox.type = 'text'; + searchBox.placeholder = 'Search tickets...'; + searchBox.className = 'search-box'; + searchBox.oninput = (e) => { + const searchTerm = e.target.value.toLowerCase(); + const rows = document.querySelectorAll('tbody tr'); + rows.forEach(row => { + const text = row.textContent.toLowerCase(); + row.style.display = text.includes(searchTerm) ? '' : 'none'; + }); + }; + document.querySelector('h1').after(searchBox); +} + +// 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'; + } + }); + }; + document.querySelector('.table-controls .table-actions').prepend(filter); +} + +function sortTable(table, column) { + const headers = table.querySelectorAll('th'); + headers.forEach(header => { + header.classList.remove('sort-asc', 'sort-desc'); + }); + + const rows = Array.from(table.querySelectorAll('tbody tr')); + const currentDirection = table.dataset.sortColumn == column + ? (table.dataset.sortDirection === 'asc' ? 'desc' : 'asc') + : 'asc'; + + table.dataset.sortColumn = column; + table.dataset.sortDirection = currentDirection; + + rows.sort((a, b) => { + const aValue = a.children[column].textContent.trim(); + const bValue = b.children[column].textContent.trim(); + + // Check if this is a date column (Created or Updated) + const headerText = headers[column].textContent.toLowerCase(); + if (headerText === 'created' || headerText === 'updated') { + const dateA = new Date(aValue); + const dateB = new Date(bValue); + return currentDirection === 'asc' ? dateA - dateB : dateB - dateA; + } + + // Existing numeric and string comparison logic + const numA = parseFloat(aValue); + const numB = parseFloat(bValue); + + if (!isNaN(numA) && !isNaN(numB)) { + return currentDirection === 'asc' ? numA - numB : numB - numA; + } + + return currentDirection === 'asc' + ? aValue.localeCompare(bValue) + : bValue.localeCompare(aValue); + }); + + const currentHeader = headers[column]; + currentHeader.classList.add(currentDirection === 'asc' ? 'sort-asc' : 'sort-desc'); + + const tbody = table.querySelector('tbody'); + rows.forEach(row => tbody.appendChild(row)); +} + +// Modify the CSS to ensure arrows are more visible +document.addEventListener('DOMContentLoaded', function() { + const tableHeaders = document.querySelectorAll('th'); + tableHeaders.forEach((header, index) => { + header.style.cursor = 'pointer'; // Make headers look clickable + header.addEventListener('click', () => { + const table = header.closest('table'); + sortTable(table, index); + }); + }); +}); + +function createHamburgerMenu() { + // Create hamburger menu container + const hamburgerMenu = document.createElement('div'); + hamburgerMenu.className = 'hamburger-menu'; + hamburgerMenu.innerHTML = ` +
+
+
+

Filters

+
+

Categories

+
+
+
+

Types

+
+
+
+ + +
+
+ `; + + // Populate categories and types from data attributes + const categoriesContainer = hamburgerMenu.querySelector('#category-filters'); + const typesContainer = hamburgerMenu.querySelector('#type-filters'); + + const categories = JSON.parse(document.body.dataset.categories || '[]'); + const types = JSON.parse(document.body.dataset.types || '[]'); + + // Create checkboxes for categories + categories.forEach(category => { + const label = document.createElement('label'); + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.value = category; + checkbox.name = 'category'; + label.appendChild(checkbox); + label.appendChild(document.createTextNode(category)); + categoriesContainer.appendChild(label); + }); + + // Create checkboxes for types + types.forEach(type => { + const label = document.createElement('label'); + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.value = type; + checkbox.name = 'type'; + label.appendChild(checkbox); + label.appendChild(document.createTextNode(type)); + typesContainer.appendChild(label); + }); + + // Toggle hamburger menu + const hamburgerIcon = hamburgerMenu.querySelector('.hamburger-icon'); + const hamburgerContent = hamburgerMenu.querySelector('.hamburger-content'); + hamburgerIcon.addEventListener('click', () => { + hamburgerContent.classList.toggle('open'); + document.body.classList.toggle('menu-open'); + }); + + // Apply filters + const applyFiltersBtn = hamburgerMenu.querySelector('#apply-filters'); + applyFiltersBtn.addEventListener('click', () => { + const selectedCategories = Array.from( + categoriesContainer.querySelectorAll('input:checked') + ).map(cb => cb.value); + + const selectedTypes = Array.from( + typesContainer.querySelectorAll('input:checked') + ).map(cb => cb.value); + + // Construct URL with filters + const params = new URLSearchParams(window.location.search); + + if (selectedCategories.length > 0) { + params.set('category', selectedCategories.join(',')); + } else { + params.delete('category'); + } + + if (selectedTypes.length > 0) { + params.set('type', selectedTypes.join(',')); + } else { + params.delete('type'); + } + + // Reload with new filters + window.location.search = params.toString(); + }); + + // Clear filters + const clearFiltersBtn = hamburgerMenu.querySelector('#clear-filters'); + clearFiltersBtn.addEventListener('click', () => { + const params = new URLSearchParams(window.location.search); + params.delete('category'); + params.delete('type'); + window.location.search = params.toString(); + }); + + // Add to body + document.body.appendChild(hamburgerMenu); + + // Close hamburger menu + const closeButton = hamburgerMenu.querySelector('.close-hamburger'); + closeButton.addEventListener('click', () => { + hamburgerContent.classList.remove('open'); + document.body.classList.remove('menu-open'); + }); +} + +// Add to DOMContentLoaded +document.addEventListener('DOMContentLoaded', createHamburgerMenu); diff --git a/assets/js/ticket.js b/assets/js/ticket.js new file mode 100644 index 0000000..8a70795 --- /dev/null +++ b/assets/js/ticket.js @@ -0,0 +1,184 @@ +function saveTicket() { + const editables = document.querySelectorAll('.editable'); + const data = {}; + const ticketId = window.location.href.split('id=')[1]; + + editables.forEach(field => { + if (field.dataset.field) { + data[field.dataset.field] = field.value; + } + }); + + fetch('update_ticket.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + ticket_id: ticketId, + ...data + }) + }) + .then(response => response.json()) + .then(data => { + if(data.success) { + const statusDisplay = document.getElementById('statusDisplay'); + statusDisplay.className = `status-${data.status}`; + statusDisplay.textContent = data.status; + } + }); +} + +function toggleEditMode() { + const editButton = document.getElementById('editButton'); + const editables = document.querySelectorAll('.editable'); + const isEditing = editButton.classList.contains('active'); + + if (!isEditing) { + // Enable editing + editButton.textContent = 'Save Changes'; + editButton.classList.add('active'); + editables.forEach(field => { + field.disabled = false; + if (field.classList.contains('title-input')) { + field.focus(); + } + }); + } else { + // Save changes and disable editing + saveTicket(); + editButton.textContent = 'Edit Ticket'; + editButton.classList.remove('active'); + editables.forEach(field => { + field.disabled = true; + }); + } +} +function addComment() { + const commentText = document.getElementById('newComment').value; + const ticketId = window.location.href.split('id=')[1]; + + fetch('add_comment.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + ticket_id: ticketId, + comment_text: commentText + }) + }) + .then(response => response.json()) + .then(data => { + if(data.success) { + // Clear the comment box + document.getElementById('newComment').value = ''; + + // Add new comment to the list + const commentsList = document.querySelector('.comments-list'); + const newComment = ` +
+
+ ${data.user_name} + ${data.created_at} +
+
${commentText}
+
+ `; + commentsList.insertAdjacentHTML('afterbegin', newComment); + } + }); +} + +function togglePreview() { + const preview = document.getElementById('markdownPreview'); + const textarea = document.getElementById('newComment'); + const isPreviewEnabled = document.getElementById('markdownToggle').checked; + + preview.style.display = isPreviewEnabled ? 'block' : 'none'; + + if (isPreviewEnabled) { + preview.innerHTML = marked.parse(textarea.value); + textarea.addEventListener('input', updatePreview); + } else { + textarea.removeEventListener('input', updatePreview); + } +} + +function updatePreview() { + const preview = document.getElementById('markdownPreview'); + const textarea = document.getElementById('newComment'); + preview.innerHTML = marked.parse(textarea.value); +} + +function toggleMarkdownMode() { + const previewToggle = document.getElementById('markdownToggle'); + const isMasterEnabled = document.getElementById('markdownMaster').checked; + + previewToggle.disabled = !isMasterEnabled; + if (!isMasterEnabled) { + previewToggle.checked = false; + document.getElementById('markdownPreview').style.display = 'none'; + } +} + +function addComment() { + const commentText = document.getElementById('newComment').value; + const isMarkdownEnabled = document.getElementById('markdownMaster').checked; + const ticketId = window.location.href.split('id=')[1]; + + fetch('add_comment.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + ticket_id: ticketId, + comment_text: commentText, + markdown_enabled: isMarkdownEnabled + }) + }) + .then(response => response.json()) + .then(data => { + if(data.success) { + const commentsList = document.querySelector('.comments-list'); + const newCommentHtml = ` +
+
+ ${data.user_name} + ${data.created_at} +
+
+ ${isMarkdownEnabled ? marked.parse(commentText) : commentText} +
+
+ `; + commentsList.insertAdjacentHTML('afterbegin', newCommentHtml); + document.getElementById('newComment').value = ''; + } + }); +} + +document.addEventListener('DOMContentLoaded', function() { + // Show description tab by default + showTab('description'); +}); + +function showTab(tabName) { + // Hide all tab contents + const descriptionTab = document.getElementById('description-tab'); + const commentsTab = document.getElementById('comments-tab'); + + // Hide both tabs + descriptionTab.style.display = 'none'; + commentsTab.style.display = 'none'; + + // Remove active class from all buttons + document.querySelectorAll('.tab-btn').forEach(btn => { + btn.classList.remove('active'); + }); + + // Show selected tab and activate its button + document.getElementById(`${tabName}-tab`).style.display = 'block'; + document.querySelector(`[onclick="showTab('${tabName}')"]`).classList.add('active'); +} diff --git a/logo.svg b/logo.svg old mode 100644 new mode 100755