577 lines
22 KiB
JavaScript
577 lines
22 KiB
JavaScript
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 = `
|
||
<div class="settings-modal">
|
||
<div class="settings-modal-header">
|
||
<h2>Dashboard Settings</h2>
|
||
<button class="close-modal">×</button>
|
||
</div>
|
||
<div class="settings-modal-content">
|
||
<div class="setting-group">
|
||
<h3>Toggle Columns</h3>
|
||
<div class="column-toggles">
|
||
<label>
|
||
<input type="checkbox" value="ticket_id" checked> Ticket ID
|
||
</label>
|
||
<label>
|
||
<input type="checkbox" value="title" checked> Title
|
||
</label>
|
||
<label>
|
||
<input type="checkbox" value="category" checked> Category
|
||
</label>
|
||
<label>
|
||
<input type="checkbox" value="type" checked> Type
|
||
</label>
|
||
<label>
|
||
<input type="checkbox" value="status" checked> Status
|
||
</label>
|
||
<label>
|
||
<input type="checkbox" value="priority" checked> Priority
|
||
</label>
|
||
<label>
|
||
<input type="checkbox" value="created" checked> Created
|
||
</label>
|
||
<label>
|
||
<input type="checkbox" value="updated" checked> Updated
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="setting-group">
|
||
<h3>Rows per Page</h3>
|
||
<select id="rows-per-page">
|
||
<option value="15">15</option>
|
||
<option value="25">25</option>
|
||
<option value="50">50</option>
|
||
<option value="100">100</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="settings-modal-footer">
|
||
<button class="save-settings">Save Settings</button>
|
||
<button class="cancel-settings">Cancel</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// 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 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');
|
||
};
|
||
|
||
// 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) {
|
||
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';
|
||
|
||
// Check if we're on a ticket page
|
||
const isTicketPage = window.location.pathname.includes('ticket.php');
|
||
|
||
if (isTicketPage) {
|
||
hamburgerMenu.innerHTML = `
|
||
<div class="hamburger-icon">☰</div>
|
||
<div class="hamburger-content">
|
||
<div class="close-hamburger">☰</div>
|
||
<h3>Ticket Controls</h3>
|
||
<div class="menu-group">
|
||
<label>Status</label>
|
||
<select class="editable" data-field="status">
|
||
<option value="Open">Open</option>
|
||
<option value="Closed">Closed</option>
|
||
</select>
|
||
</div>
|
||
<div class="menu-group">
|
||
<label>Priority</label>
|
||
<select class="editable" data-field="priority">
|
||
<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>
|
||
</select>
|
||
</div>
|
||
<div class="menu-group">
|
||
<label>Category</label>
|
||
<select class="editable" data-field="category">
|
||
<option value="Hardware">Hardware</option>
|
||
<option value="Software">Software</option>
|
||
<option value="Network">Network</option>
|
||
<option value="Security">Security</option>
|
||
<option value="Other">Other</option>
|
||
</select>
|
||
</div>
|
||
<div class="menu-group">
|
||
<label>Type</label>
|
||
<select class="editable" data-field="type">
|
||
<option value="Maintenance">Maintenance</option>
|
||
<option value="Install">Install</option>
|
||
<option value="Task">Task</option>
|
||
<option value="Upgrade">Upgrade</option>
|
||
</select>
|
||
</div>
|
||
<div class="menu-actions">
|
||
<button onclick="saveTicket()" class="btn primary">Save Changes</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
} else {
|
||
hamburgerMenu.innerHTML = `
|
||
<div class="hamburger-icon">☰</div>
|
||
<div class="hamburger-content">
|
||
<div class="close-hamburger">☰</div>
|
||
<h3>Filters</h3>
|
||
<div class="filter-section">
|
||
<h4>Categories</h4>
|
||
<div id="category-filters"></div>
|
||
</div>
|
||
<div class="filter-section">
|
||
<h4>Types</h4>
|
||
<div id="type-filters"></div>
|
||
</div>
|
||
<div class="filter-actions">
|
||
<button id="apply-filters">Apply Filters</button>
|
||
<button id="clear-filters">Clear Filters</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// 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);
|