window.TableEnhancer = { init(selector, options = {}) { const defaults = { perPage: 10, searchable: true, sortable: true, persistKey: null }; const config = { ...defaults, ...options }; document.querySelectorAll(selector).forEach(table => this.enhance(table, config)); }, enhance(table, config) { const container = table.closest('.table-container') || table.parentElement; if (!container.querySelector('.table-search')) { this.addControls(container, table, config); } this.setupSearch(container, table, config); this.setupSort(container, table, config); this.setupPagination(container, table, config); }, addControls(container, table, config) { const controls = document.createElement('div'); controls.className = 'table-controls flex justify-between items-center mb-4 p-4 bg-gray-50 rounded-t-lg'; controls.innerHTML = ` ${config.searchable ? `
` : ''}
`; container.insertBefore(controls, table); const pagination = document.createElement('div'); pagination.className = 'table-pagination flex justify-center items-center space-x-2 mt-4'; container.appendChild(pagination); }, setupSearch(container, table, config) { const searchInput = container.querySelector('.table-search'); if (!searchInput) return; const key = config.persistKey ? `table_search_${config.persistKey}` : null; if (key && localStorage.getItem(key)) { searchInput.value = localStorage.getItem(key); } searchInput.addEventListener('input', (e) => { if (key) localStorage.setItem(key, e.target.value); this.filterTable(table, e.target.value); this.updatePagination(container, table, 1, config); }); }, setupSort(container, table, config) { if (!config.sortable) return; const headers = table.querySelectorAll('th'); headers.forEach((th, index) => { if (th.textContent.trim()) { th.classList.add('cursor-pointer', 'hover:bg-gray-100'); th.dataset.sortCol = index; th.innerHTML += ` `; } }); container.querySelectorAll('th[data-sort-col]').forEach(th => { th.addEventListener('click', () => { const col = th.dataset.sortCol; const currentSort = th.dataset.sort || 'none'; const newSort = currentSort === 'asc' ? 'desc' : 'asc'; this.sortTable(table, col, newSort); th.dataset.sort = newSort; th.querySelector('.sort-icon').innerHTML = newSort === 'asc' ? '' : ''; }); }); }, setupPagination(container, table, config) { this.updatePagination(container, table, 1, config); }, filterTable(table, query) { const rows = table.querySelectorAll('tbody tr'); const lowerQuery = query.toLowerCase(); let visibleCount = 0; rows.forEach(row => { const text = row.textContent.toLowerCase(); const match = text.includes(lowerQuery); row.style.display = match ? '' : 'none'; if (match) visibleCount++; }); const infoEl = container.querySelector('.table-info'); if (infoEl) { infoEl.textContent = `${visibleCount} row${visibleCount !== 1 ? 's' : ''} found`; } }, sortTable(table, col, direction) { const tbody = table.querySelector('tbody'); const rows = Array.from(tbody.querySelectorAll('tr')); rows.sort((a, b) => { const aVal = a.querySelectorAll('td')[col]?.textContent.trim() || ''; const bVal = b.querySelectorAll('td')[col]?.textContent.trim() || ''; const aNum = parseFloat(aVal); const bNum = parseFloat(bVal); if (!isNaN(aNum) && !isNaN(bNum)) { return direction === 'asc' ? aNum - bNum : bNum - aNum; } return direction === 'asc' ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal); }); rows.forEach(row => tbody.appendChild(row)); }, updatePagination(container, table, page, config) { const rows = Array.from(table.querySelectorAll('tbody tr:not([style*=\"display: none\"])')); const totalPages = Math.ceil(rows.length / config.perPage); const pagination = container.querySelector('.table-pagination'); if (!pagination) return; const start = (page - 1) * config.perPage; const end = start + config.perPage; rows.forEach((row, index) => { row.style.display = index >= start && index < end ? '' : 'none'; }); if (totalPages <= 1) { pagination.innerHTML = ''; return; } let html = ` `; for (let i = 1; i <= totalPages; i++) { if (i === 1 || i === totalPages || (i >= page - 1 && i <= page + 1)) { html += ``; } else if (i === page - 2 || i === page + 2) { html += `...`; } } html += ` `; pagination.innerHTML = html; }, goToPage(tableId, page) { const table = tableId ? document.getElementById(tableId) : document.querySelector('table'); if (!table) return; const container = table.closest('.table-container') || table.parentElement; const config = { perPage: 10 }; this.updatePagination(container, table, page, config); }, goToPageBySelector(selector, page) { const container = document.querySelector(selector); if (!container) return; const table = container.querySelector('table'); if (!table) return; const config = { perPage: 10 }; this.updatePagination(container, table, page, config); } };