Files
tinker_tickets/src/assets/js/dashboard.js

488 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 filter = document.createElement('select');
filter.innerHTML = `
<option value="">All Status</option>
<option value="Open">Open</option>
<option value="Closed">Closed</option>
`;
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 = `
<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);