Files
tinker_tickets/assets/js/dashboard.js

622 lines
24 KiB
JavaScript
Raw Normal View History

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();
2025-03-11 20:40:20 -04:00
createHamburgerMenu();
// 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);
});
});
});
2025-03-11 20:46:28 -04:00
function toggleHamburgerEditMode() {
const editables = document.querySelectorAll('.hamburger-content .editable');
2025-03-11 20:55:04 -04:00
const editButton = document.getElementById('hamburgerEditButton');
const editModeButtons = document.getElementById('editModeButtons');
editButton.style.display = 'none';
editModeButtons.style.display = 'block';
editables.forEach(field => field.disabled = false);
}
2025-03-11 20:46:28 -04:00
2025-03-11 20:55:04 -04:00
function saveHamburgerChanges() {
saveTicket();
resetHamburgerEditMode();
}
function cancelHamburgerEdit() {
// Reset values to original
location.reload();
resetHamburgerEditMode();
}
function resetHamburgerEditMode() {
const editables = document.querySelectorAll('.hamburger-content .editable');
const editButton = document.getElementById('hamburgerEditButton');
const editModeButtons = document.getElementById('editModeButtons');
editButton.style.display = 'block';
editModeButtons.style.display = 'none';
editables.forEach(field => field.disabled = true);
2025-03-11 20:46:28 -04:00
}
function createHamburgerMenu() {
// Create hamburger menu container
const hamburgerMenu = document.createElement('div');
hamburgerMenu.className = 'hamburger-menu';
2025-03-11 20:42:38 -04:00
// Check if we're on a ticket page
const isTicketPage = window.location.pathname.includes('ticket.php');
2025-03-11 20:23:36 -04:00
2025-03-11 20:42:38 -04:00
if (isTicketPage) {
// Get current values from existing select elements
const selects = document.querySelectorAll('select.editable');
const values = {};
selects.forEach(select => {
values[select.dataset.field] = select.value;
});
2025-03-11 20:40:20 -04:00
2025-03-11 20:42:38 -04:00
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>
2025-03-11 20:52:11 -04:00
<select class="editable" data-field="status" disabled>
2025-03-11 20:42:38 -04:00
<option value="Open" ${values.status === 'Open' ? 'selected' : ''}>Open</option>
<option value="Closed" ${values.status === 'Closed' ? 'selected' : ''}>Closed</option>
</select>
2025-03-11 20:23:36 -04:00
</div>
2025-03-11 20:42:38 -04:00
<div class="menu-group">
<label>Priority</label>
2025-03-11 20:52:11 -04:00
<select class="editable" data-field="priority" disabled>
2025-03-11 20:42:38 -04:00
<option value="1" ${values.priority === '1' ? 'selected' : ''}>P1 - Critical Impact</option>
<option value="2" ${values.priority === '2' ? 'selected' : ''}>P2 - High Impact</option>
<option value="3" ${values.priority === '3' ? 'selected' : ''}>P3 - Medium Impact</option>
<option value="4" ${values.priority === '4' ? 'selected' : ''}>P4 - Low Impact</option>
</select>
2025-03-11 20:23:36 -04:00
</div>
2025-03-11 20:42:38 -04:00
<div class="menu-group">
<label>Category</label>
2025-03-11 20:52:11 -04:00
<select class="editable" data-field="category" disabled>
2025-03-11 20:42:38 -04:00
<option value="Hardware" ${values.category === 'Hardware' ? 'selected' : ''}>Hardware</option>
<option value="Software" ${values.category === 'Software' ? 'selected' : ''}>Software</option>
<option value="Network" ${values.category === 'Network' ? 'selected' : ''}>Network</option>
<option value="Security" ${values.category === 'Security' ? 'selected' : ''}>Security</option>
<option value="Other" ${values.category === 'Other' ? 'selected' : ''}>Other</option>
</select>
</div>
<div class="menu-group">
<label>Type</label>
2025-03-11 20:52:11 -04:00
<select class="editable" data-field="type" disabled>
2025-03-11 20:42:38 -04:00
<option value="Maintenance" ${values.type === 'Maintenance' ? 'selected' : ''}>Maintenance</option>
<option value="Install" ${values.type === 'Install' ? 'selected' : ''}>Install</option>
<option value="Task" ${values.type === 'Task' ? 'selected' : ''}>Task</option>
<option value="Upgrade" ${values.type === 'Upgrade' ? 'selected' : ''}>Upgrade</option>
</select>
</div>
2025-03-11 20:46:28 -04:00
<div class="menu-controls">
2025-03-11 20:52:11 -04:00
<button id="hamburgerEditButton" class="btn primary" onclick="toggleHamburgerEditMode()">Edit Ticket</button>
2025-03-11 20:55:04 -04:00
<div id="editModeButtons" style="display: none; margin-top: 10px;">
<button onclick="saveHamburgerChanges()" class="btn primary">Save Changes</button>
<button onclick="cancelHamburgerEdit()" class="btn">Cancel</button>
</div>
2025-03-11 20:46:28 -04:00
</div>
2025-03-11 20:42:38 -04:00
<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>
`;
2025-03-11 20:42:38 -04:00
// 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);
});
2025-03-11 20:42:38 -04:00
// 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);
});
2025-03-11 20:42:38 -04:00
// 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);
2025-03-11 20:42:38 -04:00
// Construct URL with filters
const params = new URLSearchParams(window.location.search);
if (selectedCategories.length > 0) {
params.set('category', selectedCategories.join(','));
} else {
2025-03-11 20:23:36 -04:00
params.delete('category');
2025-03-11 20:42:38 -04:00
}
if (selectedTypes.length > 0) {
params.set('type', selectedTypes.join(','));
} else {
2025-03-11 20:23:36 -04:00
params.delete('type');
2025-03-11 20:42:38 -04:00
}
// Reload with new filters
window.location.search = params.toString();
2025-03-11 20:26:15 -04:00
});
2025-03-11 20:42:38 -04:00
// 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();
2025-03-11 20:26:15 -04:00
});
2025-03-11 20:42:38 -04:00
}
// Add to body
document.body.appendChild(hamburgerMenu);
// 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');
});
// 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);