/* AAU CRM — reusable DataTable (IndexTable pattern) */ function DataTable({ columns, rows, getId, searchKeys, selectable, bulkActions, onRowClick, toolbar, exportName = 'export', pageSize = 8, rowClass }) { const [q, setQ] = useState(''); const [sort, setSort] = useState(null); // {key, dir} const [sel, setSel] = useState([]); const [page, setPage] = useState(0); const [cols, setCols] = useState(columns.map(c => c.key)); const [colMenu, setColMenu] = useState(false); const visCols = columns.filter(c => cols.includes(c.key)); let data = rows; if (q && searchKeys) data = data.filter(r => searchKeys.some(k => String(r[k] ?? '').toLowerCase().includes(q.toLowerCase()))); if (sort) data = [...data].sort((a, b) => { const av = a[sort.key], bv = b[sort.key]; const r = av > bv ? 1 : av < bv ? -1 : 0; return sort.dir === 'asc' ? r : -r; }); const pages = Math.max(1, Math.ceil(data.length / pageSize)); const pageData = data.slice(page * pageSize, page * pageSize + pageSize); const toggleSel = id => setSel(s => s.includes(id) ? s.filter(x => x !== id) : [...s, id]); const allSel = pageData.length > 0 && pageData.every(r => sel.includes(getId(r))); const toggleAll = () => setSel(allSel ? sel.filter(id => !pageData.some(r => getId(r) === id)) : [...new Set([...sel, ...pageData.map(getId)])]); function exportCSV() { const head = visCols.map(c => c.label).join(','); const lines = data.map(r => visCols.map(c => '"' + String(c.csv ? c.csv(r) : r[c.key] ?? '').replace(/"/g, '""') + '"').join(',')); const blob = new Blob(['\uFEFF' + head + '\n' + lines.join('\n')], { type: 'text/csv;charset=utf-8' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = exportName + '.csv'; a.click(); } function setSortKey(k) { setSort(s => s && s.key === k ? { key: k, dir: s.dir === 'asc' ? 'desc' : 'asc' } : { key: k, dir: 'asc' }); } return (
{searchKeys &&
{ setQ(v); setPage(0); }} />
} {toolbar}
{colMenu && (
{columns.map(c =>
setCols(cs => cs.includes(c.key) ? cs.filter(x => x !== c.key) : [...cs, c.key])}> {}} />{c.label}
)}
)}
{selectable && sel.length > 0 && (
{sel.length} đã chọn
{(bulkActions || []).map((b, i) => )}
)}
{selectable && } {visCols.map(c => )} {pageData.map(r => { const id = getId(r); return ( onRowClick(r) : undefined}> {selectable && } {visCols.map(c => )} ); })} {pageData.length === 0 && }
{c.sortable !== false ? setSortKey(c.key)}>{c.label}{sort && sort.key === c.key && } : c.label}
{ e.stopPropagation(); toggleSel(id); }}> {}} />{c.render ? c.render(r) : r[c.key]}
{data.length} kết quả · trang {page + 1}/{pages}
); } window.DataTable = DataTable;