almost functional

This commit is contained in:
Matt Batchelder
2026-02-04 15:26:44 -05:00
parent 2153d3c725
commit f392e5d016
39 changed files with 10115 additions and 602 deletions

View File

@@ -16,19 +16,51 @@
function initSidebarToggle() {
const toggleBtn = document.querySelector('[data-action="toggle-sidebar"]');
const sidebar = document.querySelector('.ots-sidebar');
const closeBtn = document.querySelector('.ots-sidebar-close');
const collapseBtn = document.querySelector('.sidebar-collapse-btn');
const body = document.body;
if (!toggleBtn || !sidebar) return;
if (!sidebar) return;
toggleBtn.addEventListener('click', function(e) {
e.preventDefault();
sidebar.classList.toggle('active');
});
// Handle sidebar close button
if (closeBtn) {
closeBtn.addEventListener('click', function(e) {
e.preventDefault();
sidebar.classList.remove('active');
});
}
if (toggleBtn) {
toggleBtn.addEventListener('click', function(e) {
e.preventDefault();
sidebar.classList.toggle('active');
});
}
if (collapseBtn) {
const isCollapsed = localStorage.getItem(STORAGE_KEYS.sidebarCollapsed) === 'true';
if (isCollapsed) {
sidebar.classList.add('collapsed');
body.classList.add('ots-sidebar-collapsed');
}
collapseBtn.addEventListener('click', function(e) {
e.preventDefault();
const nowCollapsed = !sidebar.classList.contains('collapsed');
sidebar.classList.toggle('collapsed');
body.classList.toggle('ots-sidebar-collapsed', nowCollapsed);
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, nowCollapsed ? 'true' : 'false');
});
}
// Initialize sidebar section toggles
initSidebarSectionToggles();
// Close sidebar when clicking outside on mobile
document.addEventListener('click', function(e) {
if (window.innerWidth <= 768) {
const isClickInsideSidebar = sidebar.contains(e.target);
const isClickOnToggle = toggleBtn.contains(e.target);
const isClickOnToggle = toggleBtn && toggleBtn.contains(e.target);
if (!isClickInsideSidebar && !isClickOnToggle && sidebar.classList.contains('active')) {
sidebar.classList.remove('active');
@@ -37,27 +69,47 @@
});
}
/**
* Initialize sidebar section collapse/expand functionality
*/
function initSidebarSectionToggles() {
const groupToggles = document.querySelectorAll('.sidebar-group-toggle');
groupToggles.forEach(toggle => {
toggle.addEventListener('click', function(e) {
e.preventDefault();
const group = toggle.closest('.sidebar-group');
const submenu = group ? group.querySelector('.sidebar-submenu') : null;
if (!submenu) return;
const isOpen = group.classList.contains('is-open');
group.classList.toggle('is-open', !isOpen);
toggle.setAttribute('aria-expanded', (!isOpen).toString());
submenu.style.display = isOpen ? 'none' : 'block';
});
});
}
/**
* Initialize dropdown menus
*/
function initDropdowns() {
const dropdowns = document.querySelectorAll('.dropdown');
dropdowns.forEach(dropdown => {
const button = dropdown.querySelector('.dropdown-menu');
if (!button) return;
const toggle = dropdown.querySelector('.dropdown-toggle, [data-toggle="dropdown"], .dt-button');
const menu = dropdown.querySelector('.dropdown-menu');
// Toggle menu on button click
dropdown.addEventListener('click', function(e) {
if (e.target.closest('.user-btn') || e.target.closest('[aria-label="User menu"]')) {
e.preventDefault();
dropdown.classList.toggle('active');
}
if (!toggle || !menu) return;
// Toggle menu on toggle click
toggle.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
dropdown.classList.toggle('active');
});
// Close menu when clicking outside
document.addEventListener('click', function(e) {
if (!dropdown.contains(e.target)) {
@@ -65,6 +117,99 @@
}
});
});
// Support DataTables Buttons collections which are not wrapped by .dropdown
document.addEventListener('click', function(e) {
const btn = e.target.closest('.dt-button');
if (btn) {
e.preventDefault();
e.stopPropagation();
const wrapper = btn.closest('.dt-buttons') || btn.parentElement;
// close other open dt-buttons collections
document.querySelectorAll('.dt-buttons.active').forEach(w => {
if (w !== wrapper) w.classList.remove('active');
});
wrapper.classList.toggle('active');
// If DataTables placed the collection on the body, find it and position it under the clicked button
const allCollections = Array.from(document.querySelectorAll('.dt-button-collection'));
let collection = wrapper.querySelector('.dt-button-collection') || allCollections.find(c => !wrapper.contains(c));
// If DataTables didn't create a collection element, create one as a fallback
if (!collection) {
collection = document.createElement('div');
collection.className = 'dt-button-collection';
// prefer to append near wrapper for positioning; fallback to body
(wrapper || document.body).appendChild(collection);
}
if (collection) {
// hide other collections
allCollections.forEach(c => { if (c !== collection) { c.classList.remove('show'); c.style.display = 'none'; } });
const rect = btn.getBoundingClientRect();
const top = rect.bottom + window.scrollY;
const left = rect.left + window.scrollX;
collection.style.position = 'absolute';
collection.style.top = `${top}px`;
collection.style.left = `${left}px`;
collection.style.display = 'block';
collection.classList.add('show');
// DEBUG: log collection contents
try {
console.log('dt-button-collection opened, children:', collection.children.length, collection);
} catch (err) {}
// If the collection is empty or visually empty, build a fallback column list from the nearest table
const isEmpty = collection.children.length === 0 || collection.textContent.trim() === '' || collection.offsetHeight < 10;
if (isEmpty) {
try {
let table = btn.closest('table') || wrapper.querySelector('table') || document.querySelector('table');
if (table && window.jQuery && jQuery.fn && jQuery.fn.dataTable && jQuery.fn.dataTable.isDataTable(table)) {
const dt = jQuery(table).DataTable();
// clear existing
collection.innerHTML = '';
const thead = table.querySelectorAll('thead th');
thead.forEach((th, idx) => {
const text = (th.textContent || th.innerText || `Column ${idx+1}`).trim();
const item = document.createElement('div');
item.style.padding = '6px 12px';
item.style.display = 'flex';
item.style.alignItems = 'center';
item.style.gap = '8px';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = dt.column(idx).visible();
checkbox.addEventListener('change', function() {
dt.column(idx).visible(this.checked);
});
const label = document.createElement('span');
label.textContent = text;
label.style.color = 'var(--color-text-primary)';
item.appendChild(checkbox);
item.appendChild(label);
collection.appendChild(item);
});
console.log('Fallback: populated collection with', collection.children.length, 'items');
} else {
console.log('Fallback: no DataTable instance found to populate column visibility');
}
} catch (err) {
console.warn('Error building fallback column list', err);
}
}
}
return;
}
// click outside dt-button -> close any open collections
document.querySelectorAll('.dt-buttons.active').forEach(w => w.classList.remove('active'));
});
}
/**