Add pagination (Prev/Next with counter) and X-Total-Count header

This commit is contained in:
2026-05-22 17:57:24 -04:00
parent cce5cf2b5f
commit 95b5bd26eb
2 changed files with 58 additions and 4 deletions
+48 -2
View File
@@ -242,6 +242,8 @@
.btn-outline { background: transparent; border: 1.5px solid var(--border); color: var(--text); }
.btn-danger { background: transparent; border: 1.5px solid var(--red); color: var(--red); }
.btn-green { background: var(--green); color: #000; }
.btn-xs { padding: 6px 12px; font-size: 12px; width: auto; line-height: 1; }
.pagination-bar { display: flex; align-items: center; gap: 6px; }
/* ═══════════════════════════════════════════════════════════════════════
INPUTS
@@ -1070,6 +1072,7 @@
<button class="btn btn-outline btn-sm import-btn" onclick="showImportView()">📥 Import</button>
</div>
<div id="assetList"></div>
<div id="pagination" class="pagination-bar" style="display:none;margin-top:8px;"></div>
</div>
<!-- ── Detail View ──────────────────────────────────────────────────── -->
@@ -1297,6 +1300,8 @@
// API WRAPPER
// =========================================================================
async function api(url, opts = {}) {
const returnMeta = opts._returnMeta;
delete opts._returnMeta;
// Auto-attach auth token if available
if (AppState.authToken) {
opts.headers = opts.headers || {};
@@ -1314,6 +1319,7 @@
const detail = (data && data.detail) || text || `HTTP ${res.status}`;
throw new Error(detail);
}
if (returnMeta) return { data, headers: res.headers };
return data;
} catch (e) {
if (e.name === 'TypeError' && e.message === 'Failed to fetch') {
@@ -3014,6 +3020,9 @@
// ── Filter state ──────────────────────────────────────────────────────
let assetFilters = { category: null, status: null, make: null, disney_park: null, q: '' };
let assetOffset = 0;
const ASSET_PAGE_SIZE = 100;
let assetTotal = 0;
let assetDebounce = null;
let cachedAssets = [];
@@ -3053,10 +3062,13 @@
if (assetFilters.make) params.set('make', assetFilters.make);
if (assetFilters.q) params.set('q', assetFilters.q);
if (assetFilters.assigned_to) params.set('assigned_to', assetFilters.assigned_to);
params.set('limit', '100');
params.set('limit', String(ASSET_PAGE_SIZE));
params.set('offset', String(assetOffset));
try {
var assets = await api('/api/assets?' + params.toString());
var result = await api('/api/assets?' + params.toString(), {_returnMeta: true});
var assets = result.data;
assetTotal = parseInt(result.headers.get('X-Total-Count')) || assets.length;
// Client-side filter fallback if server doesn't support assigned_to
var filtered = assetFilters.assigned_to
? assets.filter(function(a) { return a.assigned_to === assetFilters.assigned_to; })
@@ -3066,6 +3078,7 @@
: filtered;
renderAssetList(filtered);
renderFilterPills(assets);
renderPagination();
} catch (e) {
document.getElementById('assetList').innerHTML =
'<div class="empty-state">Failed to load assets</div>';
@@ -3138,14 +3151,47 @@
assetFilters.status = status;
assetFilters.make = make;
assetFilters.disney_park = disney_park;
assetOffset = 0;
loadAssets();
}
function clearAssetSearch() {
document.getElementById('assetSearch').value = '';
assetOffset = 0;
loadAssets();
}
// ── Pagination ──────────────────────────────────────────────────────────
function renderPagination() {
const el = document.getElementById('pagination');
if (assetTotal <= ASSET_PAGE_SIZE) {
el.style.display = 'none';
return;
}
el.style.display = 'flex';
const current = assetOffset + 1;
const end = Math.min(assetOffset + ASSET_PAGE_SIZE, assetTotal);
el.innerHTML =
'<span style="font-size:12px;color:var(--text2);margin-right:auto;">' +
current + '' + end + ' of ' + assetTotal + '</span>' +
'<button class="btn btn-outline btn-xs" onclick="prevPage()" ' + (assetOffset === 0 ? 'disabled' : '') + '> Prev</button>' +
'<button class="btn btn-outline btn-xs" onclick="nextPage()" ' + (end >= assetTotal ? 'disabled' : '') + '>Next </button>';
}
function prevPage() {
if (assetOffset > 0) {
assetOffset = Math.max(0, assetOffset - ASSET_PAGE_SIZE);
_loadAssets();
}
}
function nextPage() {
if (assetOffset + ASSET_PAGE_SIZE < assetTotal) {
assetOffset += ASSET_PAGE_SIZE;
_loadAssets();
}
}
// ── View switching ────────────────────────────────────────────────────
function hideAllAssetViews() {
['assetsListView','assetsDetailView','assetsEditView','assetsImportView'].forEach(id => {