JSFiddle - React, Tailwind, and code Playground

by graphettion

HTML

<div class="container">
      <h1>Dynamic Data Table</h1>

      <div class="controls">
        <div class="search-container">
          <input type="text" id="searchInput" placeholder="Search..." />
          <span class="search-icon">🔍</span>
        </div>

        <div class="filter-container">
          <select id="statusFilter">
            <option value="">All Statuses</option>
            <option value="active">Active</option>
            <option value="pending">Pending</option>
            <option value="inactive">Inactive</option>
          </select>

          <select id="categoryFilter">
            <option value="">All Categories</option>
            <!-- Will be populated from API data -->
          </select>

          <button id="resetButton" class="reset">Reset Filters</button>
        </div>
      </div>

      <div id="tableContainer" class="table-container">
        <div class="loading-container">
          <div class="spinner"></div>
        </div>
      </div>

      <div class="pagination">
        <div class="page-info">
          Showing <span id="startEntry">0</span> to
          <span id="endEntry">0</span> of
          <span id="totalEntries">0</span> entries
        </div>

        <div class="page-controls">
          <button id="prevButton" disabled>Prev</button>
          <button id="nextButton" disabled>Next</button>
        </div>
      </div>
    </div>
  </body>
</html>

CSS

:root {
  --primary-color: #3498db;
  --primary-hover: #2980b9;
  --secondary-color: #e9e9e9;
  --text-color: #333;
  --border-color: #ddd;
  --success-color: #2ecc71;
  --warning-color: #f39c12;
  --danger-color: #e74c3c;
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
  line-height: 1.6;
  color: var(--text-color);
  background-color: #f8f9fa;
  padding: 20px;
  max-width: 1200px;
  margin: 0 auto;
}

.container {
  background-color: white;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  padding: 20px;
  margin-bottom: 20px;
}

h1 {
  color: var(--primary-color);
  margin-bottom: 20px;
  font-weight: 600;
}

.controls {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  flex-wrap: wrap;
  gap: 10px;
}

.search-container {
  position: relative;
  flex: 1;
  min-width: 200px;
  max-width: 400px;
}

.search-container input {
  width: 100%;
  padding: 10px 15px;
  border: 1px solid var(--border-color);
  border-radius: 4px;
  font-size: 16px;
  transition: border-color 0.2s;
}

.search-container input:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.25);
}

.search-icon {
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  color: #999;
}

.filter-container {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

select {
  padding: 10px;
  border: 1px solid var(--border-color);
  border-radius: 4px;
  background-color: white;
  min-width: 120px;
  font-size: 16px;
}

select:focus {
  outline: none;
  border-color: var(--primary-color);
}

button {
  background-color: var(--primary-color);
  color: white;
  border: none;
  padding: 10px 15px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
  transition: background-color 0.2s;
}

button:hover {
  background-color: var(--primary-hover);
}

button.reset {
 ...

JavaScript

// Configuration
const config = {
  itemsPerPage: 5,
  mockApiDelay: 500, // Simulates network latency
  mockErrorRate: 0, // Set between 0-1 to simulate API errors (e.g., 0.2 for 20%)
}

// State management
const state = {
  data: [],
  filteredData: [],
  currentPage: 0,
  sortColumn: "id",
  sortDirection: "asc",
  searchTerm: "",
  statusFilter: "",
  categoryFilter: "",
  loading: true,
  error: null,
}

// DOM Elements
const elements = {
  tableContainer: document.getElementById("tableContainer"),
  searchInput: document.getElementById("searchInput"),
  statusFilter: document.getElementById("statusFilter"),
  categoryFilter: document.getElementById("categoryFilter"),
  resetButton: document.getElementById("resetButton"),
  prevButton: document.getElementById("prevButton"),
  nextButton: document.getElementById("nextButton"),
  startEntry: document.getElementById("startEntry"),
  endEntry: document.getElementById("endEntry"),
  totalEntries: document.getElementById("totalEntries"),
}

// Mock data for the API
const mockData = [
  {
    id: 1,
    name: "Wireless Headphones",
    category: "Electronics",
    price: 99.99,
    status: "active",
    createdAt: "2025-01-05T08:12:34",
  },
  {
    id: 2,
    name: "Smart Watch",
    category: "Electronics",
    price: 159.99,
    status: "active",
    createdAt: "2025-01-10T11:23:45",
  },
  {
    id: 3,
    name: "Ergonomic Desk Chair",
    category: "Furniture",
    price: 249.95,
    status: "inactive",
    createdAt: "2025-01-12T09:34:56",
  },
  {
    id: 4,
    name: "LED Desk Lamp",
    category: "Home",
    price: 39.99,
    status: "active",
    createdAt: "2025-01-15T14:45:23",
  },
  {
    id: 5,
    name: "Mechanical Keyboard",
    category: "Electronics",
    price: 129.99,
    status: "pending",
    createdAt: "2025-01-18T16:56:12",
  },
  {
    id: 6,
    name: "Coffee Maker",
    category: "Kitchen",
    price: 79.99,
    status: "active",
    createdAt: "2025-01-20T10:23:45",
  },
  {
    id: 7,
...