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:
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user