fixes and refactor

This commit is contained in:
HotSwapp
2025-08-14 19:16:28 -05:00
parent 5111079149
commit bfc04a6909
61 changed files with 5689 additions and 767 deletions

View File

@@ -5,6 +5,8 @@ let isEditing = false;
let editingCustomerId = null;
let selectedCustomerIds = new Set();
let customerCompactMode = false;
let customerFocusIndex = -1;
let _customerNavInitialized = false;
// Local debounce fallback to avoid dependency on main.js
function _localDebounce(func, wait) {
@@ -49,6 +51,7 @@ function displayCustomers(customers) {
}
customers.forEach(customer => {
const rowIndex = tbody.children.length;
const phones = Array.isArray(customer.phone_numbers) ? customer.phone_numbers : [];
const primaryPhone = phones.length > 0 ? (phones[0].phone || '') : '';
const phoneCount = phones.length;
@@ -58,6 +61,8 @@ function displayCustomers(customers) {
// Store customer ID as data attribute to avoid escaping issues in onclick
row.dataset.customerId = customer.id;
row.dataset.rowIndex = String(rowIndex);
row.setAttribute('tabindex', '-1');
// Build clean, simple row structure with clickable rows (no inline onclick to avoid backslash issues)
const pad = customerCompactMode ? 'px-3 py-2' : 'px-6 py-4';
@@ -122,11 +127,16 @@ function displayCustomers(customers) {
e.stopPropagation();
editCustomer(customer.id);
});
// Focus management for keyboard navigation
row.addEventListener('mouseenter', () => setCustomerFocus(rowIndex));
row.addEventListener('click', () => setCustomerFocus(rowIndex));
tbody.appendChild(row);
});
// No select-all
refreshCustomerKeyboardRows();
}
// Helper functions
@@ -803,12 +813,13 @@ function enhanceCustomerTableRows() {
function initializeCustomerListEnhancer() {
const tbody = document.getElementById('customersTableBody');
if (!tbody || window._customerListObserver) return;
const debouncedEnhance = (typeof window.debounce === 'function' ? window.debounce : _localDebounce)(() => enhanceCustomerTableRows(), 10);
const debouncedEnhance = (typeof window.debounce === 'function' ? window.debounce : _localDebounce)(() => { enhanceCustomerTableRows(); refreshCustomerKeyboardRows(); }, 10);
const observer = new MutationObserver(() => debouncedEnhance());
observer.observe(tbody, { childList: true, subtree: false });
window._customerListObserver = observer;
// Initial pass
enhanceCustomerTableRows();
initializeCustomerListKeyboardNav();
}
// Selection helpers
@@ -878,4 +889,79 @@ function onSelectAllChange(checked) {
// Expose helpers
window.initializeCustomerListState = initializeCustomerListState;
window.toggleCompactMode = toggleCompactMode;
window.onSelectAllChange = onSelectAllChange;
window.onSelectAllChange = onSelectAllChange;
// Keyboard navigation for customer list
function initializeCustomerListKeyboardNav() {
if (_customerNavInitialized) return;
_customerNavInitialized = true;
document.addEventListener('keydown', (e) => {
const active = document.activeElement || e.target;
const tag = active && active.tagName ? active.tagName.toUpperCase() : '';
const isTyping = tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || (active && active.isContentEditable);
if (isTyping) return;
const tbody = document.getElementById('customersTableBody');
if (!tbody || tbody.children.length === 0) return;
switch (e.key) {
case 'ArrowDown': e.preventDefault(); moveCustomerFocus(1); break;
case 'ArrowUp': e.preventDefault(); moveCustomerFocus(-1); break;
case 'PageDown': e.preventDefault(); moveCustomerFocus(10); break;
case 'PageUp': e.preventDefault(); moveCustomerFocus(-10); break;
case 'Home': e.preventDefault(); setCustomerFocus(0); break;
case 'End': e.preventDefault(); setCustomerFocus(tbody.children.length - 1); break;
case 'Enter': e.preventDefault(); openFocusedCustomer(); break;
}
}, { passive: false });
}
function refreshCustomerKeyboardRows() {
const tbody = document.getElementById('customersTableBody');
if (!tbody) return;
const rows = Array.from(tbody.querySelectorAll('tr'));
rows.forEach((row, idx) => {
row.dataset.rowIndex = String(idx);
if (!row.hasAttribute('tabindex')) row.setAttribute('tabindex', '-1');
if (!row._navBound) {
row.addEventListener('mouseenter', () => setCustomerFocus(idx));
row.addEventListener('click', () => setCustomerFocus(idx));
row._navBound = true;
}
});
if (customerFocusIndex < 0 && rows.length > 0) setCustomerFocus(0);
}
function setCustomerFocus(index) {
const tbody = document.getElementById('customersTableBody');
if (!tbody) return;
const rows = Array.from(tbody.querySelectorAll('tr'));
if (rows.length === 0) { customerFocusIndex = -1; return; }
const clamped = Math.max(0, Math.min(index, rows.length - 1));
if (clamped === customerFocusIndex) return;
if (customerFocusIndex >= 0 && rows[customerFocusIndex]) {
rows[customerFocusIndex].classList.remove('ring-2', 'ring-blue-400', 'dark:ring-blue-500', 'bg-blue-50', 'dark:bg-blue-900/30');
}
customerFocusIndex = clamped;
const row = rows[customerFocusIndex];
if (!row) return;
row.classList.add('ring-2', 'ring-blue-400', 'dark:ring-blue-500', 'bg-blue-50', 'dark:bg-blue-900/30');
try { row.scrollIntoView({ block: 'nearest' }); } catch (_) {}
}
function moveCustomerFocus(delta) {
const next = (customerFocusIndex < 0 ? 0 : customerFocusIndex) + delta;
setCustomerFocus(next);
}
function openFocusedCustomer() {
const tbody = document.getElementById('customersTableBody');
if (!tbody || customerFocusIndex < 0) return;
const row = tbody.querySelector(`tr[data-row-index="${customerFocusIndex}"]`) || Array.from(tbody.querySelectorAll('tr'))[customerFocusIndex];
const id = row && row.dataset ? row.dataset.customerId : null;
if (id) viewCustomer(id);
}
// Expose for external usage/debugging
window.initializeCustomerListKeyboardNav = initializeCustomerListKeyboardNav;
window.refreshCustomerKeyboardRows = refreshCustomerKeyboardRows;
window.setCustomerFocus = setCustomerFocus;
window.openFocusedCustomer = openFocusedCustomer;