feat: click-to-sort table headers with toggle direction
This commit is contained in:
+32
-25
@@ -168,12 +168,6 @@
|
||||
<select id="filter-provider">
|
||||
<option value="">Все провайдеры</option>
|
||||
</select>
|
||||
<select id="filter-sort">
|
||||
<option value="arena_text_rank">Сортировка: Text Arena Rank</option>
|
||||
<option value="arena_agent_rank">Сортировка: Agent Arena Rank</option>
|
||||
<option value="uptime_pct">Сортировка: Uptime %</option>
|
||||
<option value="provider">Сортировка: Провайдер</option>
|
||||
</select>
|
||||
<label>
|
||||
<input type="checkbox" id="filter-arena-only"> Только с рейтингом
|
||||
</label>
|
||||
@@ -280,27 +274,23 @@ function applyFilters() {
|
||||
const prov = document.getElementById('filter-provider').value;
|
||||
const arenaOnly = document.getElementById('filter-arena-only').checked;
|
||||
const search = document.getElementById('search').value.toLowerCase();
|
||||
const sortCol = document.getElementById('filter-sort').value;
|
||||
const sortCol = currentSort.col;
|
||||
const asc = currentSort.asc;
|
||||
|
||||
let data = [...allData];
|
||||
if (prov) data = data.filter(m => m.provider === prov);
|
||||
if (arenaOnly) data = data.filter(m => m.arena_text_score);
|
||||
if (search) data = data.filter(m => m.id.toLowerCase().includes(search));
|
||||
|
||||
// sort
|
||||
// sort (null всегда внизу независимо от направления)
|
||||
const dir = asc ? 1 : -1;
|
||||
data.sort((a, b) => {
|
||||
if (sortCol === 'arena_text_rank' || sortCol === 'arena_agent_rank') {
|
||||
const av = a[sortCol], bv = b[sortCol];
|
||||
if (av == null && bv == null) return 0;
|
||||
if (av == null) return 1;
|
||||
if (bv == null) return -1;
|
||||
return av - bv;
|
||||
}
|
||||
if (sortCol === 'uptime_pct') {
|
||||
const av = a[sortCol] ?? -1, bv = b[sortCol] ?? -1;
|
||||
return bv - av;
|
||||
}
|
||||
return (a[sortCol] || '').localeCompare(b[sortCol] || '');
|
||||
if (typeof av === 'number' && typeof bv === 'number') return (av - bv) * dir;
|
||||
return String(av).localeCompare(String(bv)) * dir;
|
||||
});
|
||||
|
||||
const tbody = document.getElementById('table-body');
|
||||
@@ -316,21 +306,39 @@ function applyFilters() {
|
||||
tbody.innerHTML = data.map(renderRow).join('');
|
||||
}
|
||||
|
||||
updateSortHeaders();
|
||||
updateStats(data);
|
||||
}
|
||||
|
||||
function updateSortHeaders() {
|
||||
document.querySelectorAll('#main-table thead th[data-col]').forEach(th => {
|
||||
const icon = th.querySelector('.sort-icon');
|
||||
if (th.dataset.col === currentSort.col) {
|
||||
th.classList.add('active');
|
||||
icon.textContent = currentSort.asc ? '↑' : '↓';
|
||||
} else {
|
||||
th.classList.remove('active');
|
||||
icon.textContent = '↕';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('#main-table thead th[data-col]').forEach(th => {
|
||||
th.addEventListener('click', () => {
|
||||
const col = th.dataset.col;
|
||||
if (currentSort.col === col) {
|
||||
currentSort.asc = !currentSort.asc; // повторный клик — инверсия
|
||||
} else {
|
||||
currentSort = { col, asc: true };
|
||||
}
|
||||
applyFilters();
|
||||
});
|
||||
});
|
||||
|
||||
async function loadData() {
|
||||
document.getElementById('loading').style.display = 'block';
|
||||
document.getElementById('main-table').style.display = 'none';
|
||||
|
||||
const sort = document.getElementById('filter-sort').value;
|
||||
const prov = document.getElementById('filter-provider').value;
|
||||
const arenaOnly = document.getElementById('filter-arena-only').checked;
|
||||
|
||||
let url = `/api/models?sort=${sort}`;
|
||||
if (prov) url += `&provider=${encodeURIComponent(prov)}`;
|
||||
if (arenaOnly) url += `&arena_only=true`;
|
||||
|
||||
const res = await fetch('/api/models');
|
||||
allData = await res.json();
|
||||
|
||||
@@ -386,7 +394,6 @@ document.getElementById('btn-sync').addEventListener('click', async () => {
|
||||
});
|
||||
document.getElementById('filter-provider').addEventListener('change', applyFilters);
|
||||
document.getElementById('filter-arena-only').addEventListener('change', applyFilters);
|
||||
document.getElementById('filter-sort').addEventListener('change', applyFilters);
|
||||
document.getElementById('search').addEventListener('input', applyFilters);
|
||||
|
||||
(async () => {
|
||||
|
||||
Reference in New Issue
Block a user