Add sortable columns and inline search filter

- Implemented sortable table headers with visual indicators (▲▼)
- Click any column to sort ascending/descending
- Sort state persists during auto-refresh
- Added compact inline search filter in toolbar
- Unified search across all columns (Sim Source, Artifacts, Date, Uploaded By)
- Positioned search on right side of toolbar for cleaner layout
- Real-time filtering as you type
- Combined filtering and sorting work seamlessly together

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-15 08:45:31 -05:00
parent 4bc8310070
commit 4d9d235111
3 changed files with 186 additions and 7 deletions

View File

@@ -11,6 +11,11 @@ let autoRefreshEnabled = true;
let autoRefreshInterval = null;
const REFRESH_INTERVAL_MS = 5000; // 5 seconds
// Sorting and filtering
let allArtifacts = []; // Store all artifacts for client-side sorting/filtering
let currentSortColumn = null;
let currentSortDirection = 'asc';
// Load API info on page load
window.addEventListener('DOMContentLoaded', () => {
loadApiInfo();
@@ -37,12 +42,13 @@ async function loadArtifacts(limit = pageSize, offset = 0) {
const response = await fetch(`${API_BASE}/artifacts/?limit=${limit}&offset=${offset}`);
const artifacts = await response.json();
allArtifacts = artifacts; // Store for sorting/filtering
displayArtifacts(artifacts);
updatePagination(artifacts.length);
} catch (error) {
console.error('Error loading artifacts:', error);
document.getElementById('artifacts-tbody').innerHTML = `
<tr><td colspan="10" class="loading" style="color: #ef4444;">
<tr><td colspan="5" class="loading" style="color: #ef4444;">
Error loading artifacts: ${error.message}
</td></tr>
`;
@@ -59,7 +65,13 @@ function displayArtifacts(artifacts) {
return;
}
tbody.innerHTML = artifacts.map(artifact => `
// Apply current sort if active
let displayedArtifacts = artifacts;
if (currentSortColumn) {
displayedArtifacts = applySorting([...artifacts]);
}
tbody.innerHTML = displayedArtifacts.map(artifact => `
<tr>
<td>${artifact.test_suite || '-'}</td>
<td>
@@ -82,7 +94,7 @@ function displayArtifacts(artifacts) {
</tr>
`).join('');
document.getElementById('artifact-count').textContent = `${artifacts.length} artifacts`;
document.getElementById('artifact-count').textContent = `${displayedArtifacts.length} artifacts`;
// Re-initialize Lucide icons for dynamically added content
lucide.createIcons();
@@ -504,3 +516,76 @@ function toggleAutoRefresh() {
}
}
}
// Apply sorting to artifacts array
function applySorting(artifacts) {
if (!currentSortColumn) return artifacts;
return artifacts.sort((a, b) => {
let aVal = a[currentSortColumn] || '';
let bVal = b[currentSortColumn] || '';
// Handle date sorting
if (currentSortColumn === 'created_at') {
aVal = new Date(aVal).getTime();
bVal = new Date(bVal).getTime();
} else {
// String comparison (case insensitive)
aVal = String(aVal).toLowerCase();
bVal = String(bVal).toLowerCase();
}
if (aVal < bVal) return currentSortDirection === 'asc' ? -1 : 1;
if (aVal > bVal) return currentSortDirection === 'asc' ? 1 : -1;
return 0;
});
}
// Sorting functionality
function sortTable(column) {
// Toggle sort direction if clicking same column
if (currentSortColumn === column) {
currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc';
} else {
currentSortColumn = column;
currentSortDirection = 'asc';
}
// Update sort indicators
document.querySelectorAll('th.sortable').forEach(th => {
th.classList.remove('sort-asc', 'sort-desc');
});
const sortedHeader = event.target.closest('th');
sortedHeader.classList.add(`sort-${currentSortDirection}`);
// Apply filter and sort
filterTable();
}
// Filtering functionality - searches across all columns
function filterTable() {
const searchTerm = document.getElementById('filter-search').value.toLowerCase();
const filteredArtifacts = allArtifacts.filter(artifact => {
if (!searchTerm) return true;
// Search across all relevant fields
const searchableText = [
artifact.test_suite || '',
artifact.filename || '',
artifact.test_name || '',
formatDate(artifact.created_at)
].join(' ').toLowerCase();
return searchableText.includes(searchTerm);
});
displayArtifacts(filteredArtifacts);
}
// Clear all filters
function clearFilters() {
document.getElementById('filter-search').value = '';
filterTable();
}