import { Module } from '../core/module.js'; /** * Admin Data Table Module * * Provides interactive functionality for admin data tables: * - AJAX loading and pagination * - Sorting by columns * - Search/filtering * - Auto-refresh */ export class AdminDataTable extends Module { constructor(element) { super(element); this.resource = element.dataset.resource; this.apiEndpoint = element.dataset.apiEndpoint; this.sortable = element.dataset.sortable === 'true'; this.searchable = element.dataset.searchable === 'true'; this.paginated = element.dataset.paginated === 'true'; this.perPage = parseInt(element.dataset.perPage) || 25; this.currentPage = 1; this.currentSort = null; this.currentSearch = ''; } async init() { if (!this.apiEndpoint) { console.warn('AdminDataTable: No API endpoint configured'); return; } this.tbody = this.element.querySelector('tbody'); this.thead = this.element.querySelector('thead'); if (!this.tbody) { console.warn('AdminDataTable: No tbody found'); return; } this.setupEventListeners(); // Only load data if table is initially empty if (this.tbody.children.length === 0) { await this.loadData(); } } setupEventListeners() { // Sortable columns if (this.sortable && this.thead) { this.thead.querySelectorAll('th').forEach((th, index) => { const columnKey = th.dataset.column || this.getColumnKeyFromIndex(index); if (columnKey) { th.style.cursor = 'pointer'; th.classList.add('sortable'); th.addEventListener('click', () => { this.sortByColumn(columnKey, th); }); } }); } // Search input if (this.searchable) { const searchInput = this.getSearchInput(); if (searchInput) { searchInput.addEventListener('input', this.debounce(() => { this.currentSearch = searchInput.value; this.currentPage = 1; this.loadData(); }, 300)); } } // Pagination links (if exist) this.setupPaginationListeners(); } setupPaginationListeners() { const paginationContainer = this.getPaginationContainer(); if (!paginationContainer) return; paginationContainer.addEventListener('click', (e) => { const pageLink = e.target.closest('[data-page]'); if (pageLink) { e.preventDefault(); const page = parseInt(pageLink.dataset.page); if (page && page !== this.currentPage) { this.currentPage = page; this.loadData(); } } }); } async sortByColumn(columnKey, th) { // Toggle sort direction const currentDir = th.dataset.sortDir; const newDir = currentDir === 'asc' ? 'desc' : 'asc'; // Update sort state this.currentSort = { column: columnKey, direction: newDir }; // Update UI this.thead.querySelectorAll('th').forEach(header => { header.removeAttribute('data-sort-dir'); header.classList.remove('sort-asc', 'sort-desc'); }); th.dataset.sortDir = newDir; th.classList.add(`sort-${newDir}`); // Reload data await this.loadData(); } async loadData() { const params = new URLSearchParams({ page: this.currentPage.toString(), per_page: this.perPage.toString(), }); if (this.currentSearch) { params.set('search', this.currentSearch); } if (this.currentSort) { params.set('sort_by', this.currentSort.column); params.set('sort_dir', this.currentSort.direction); } const url = `${this.apiEndpoint}?${params}`; try { this.setLoadingState(true); const response = await fetch(url); const result = await response.json(); if (result.success) { this.renderRows(result.data); if (this.paginated && result.pagination) { this.renderPagination(result.pagination); } } else { this.showError(result.error || 'Failed to load data'); } } catch (error) { console.error('AdminDataTable: Failed to load data', error); this.showError('Failed to load data'); } finally { this.setLoadingState(false); } } renderRows(data) { if (!data || data.length === 0) { this.tbody.innerHTML = this.getEmptyRow(); return; } // Get column keys from header const columnKeys = this.getColumnKeys(); this.tbody.innerHTML = data.map(item => { const cells = columnKeys.map(key => { const value = item[key] !== undefined ? item[key] : ''; return `