JSFiddle - React, Tailwind, and code Playground

by Imri Paloja

JavaScript

/**
 * Sort HTML table by column index
 * @param {string} tableClass - class name of the table
 * @param {number} colIndex - column index to sort by (0-based)
 */
function sortTable(tableClass, colIndex) {
  const table = document.querySelector(`.${tableClass}`);
  if (!table) return;

  const tbody = table.tBodies[0];
  const rows = Array.from(tbody.rows);

  // Get current sort direction from data attribute (or default to asc)
  const header = table.querySelectorAll('th')[colIndex];
  const currentDir = header?.dataset.sortDir || 'asc';
  const direction = currentDir === 'asc' ? 'desc' : 'asc';

  // Clean previous sort indicators
  table.querySelectorAll('th').forEach((th) => {
    th.dataset.sortDir = '';
    th.classList.remove('sort-asc', 'sort-desc');
  });

  // Set new direction
  if (header) {
    header.dataset.sortDir = direction;
    header.classList.add(direction === 'asc' ? 'sort-asc' : 'sort-desc');
  }

  // Natural compare function (handles numbers, strings, dashes, etc.)
  rows.sort((a, b) => {
    let x = getCellValue(a, colIndex);
    let y = getCellValue(b, colIndex);

    // Handle special dash case
    if (x === '-' && y !== '-') return direction === 'asc' ? -1 : 1;
    if (y === '-' && x !== '-') return direction === 'asc' ? 1 : -1;
    if (x === '-' && y === '-') return 0;

    // Natural compare
    return naturalCompare(x, y) * (direction === 'asc' ? 1 : -1);
  });

  // Re-attach rows in new order (very efficient)
  rows.forEach((row) => tbody.appendChild(row));
}

// Extract clean value from cell
function getCellValue(row, index) {
  const cell = row.cells[index];
  if (!cell) return '';

  let text = cell.textContent || cell.innerText || '';

  // Remove extra spaces
  text = text.trim();

  // Handle common special cases
  if (text === '-' || text === '—' || text === '–') return '-';
  if (text === '' || text === 'N/A' || text === 'n/a') return '';

  // Try to convert to...