inventory/inventory/static/js/table.js
2025-08-27 12:04:14 -05:00

112 lines
3.6 KiB
JavaScript

function Table(cfg) {
return {
id: cfg.id,
refreshUrl: cfg.refreshUrl,
headers: cfg.headers || [],
fields: cfg.fields || [],
// external API
perPage: cfg.perPage || 10,
offset: cfg.offset || 0,
// derived + server-fed state
page: Math.floor((cfg.offset || 0) / (cfg.perPage || 10)) + 1,
total: 0,
pages: 0,
init() {
if (this.refreshUrl) this.refresh();
},
buildRefreshUrl() {
if (!this.refreshUrl) return null;
const u = new URL(this.refreshUrl, window.location.origin);
// We want server-side pagination with page/per_page
u.searchParams.set('page', this.page);
u.searchParams.set('per_page', this.perPage);
// Send requested fields in the way your backend expects
// If your route only supports "fields=a,b,c", then use:
if (this.fields.length) u.searchParams.set('fields_csv', this.fields.join(','));
return u.toString();
},
async refresh() {
const url = this.buildRefreshUrl();
if (!url) return;
const res = await fetch(url, { headers: { 'X-Requested-With': 'fetch' } });
const html = await res.text();
// Dump the server-rendered <tr> rows into the tbody
if (this.$refs.body) this.$refs.body.innerHTML = html;
// Read pagination metadata from headers
const toInt = (v, d=0) => {
const n = parseInt(v ?? '', 10);
return Number.isFinite(n) ? n : d;
};
const total = toInt(res.headers.get('X-Total'));
const pages = toInt(res.headers.get('X-Pages'));
const page = toInt(res.headers.get('X-Page'), this.page);
const per = toInt(res.headers.get('X-Per-Page'), this.perPage);
// Update local state
this.total = total;
this.pages = pages;
this.page = page;
this.perPage = per;
this.offset = (this.page - 1) * this.perPage;
// Update pager UI (if you put <ul x-ref="pagination"> in your caption)
this.buildPager();
// Caption numbers are bound via x-text so they auto-update.
},
buildPager() {
const ul = this.$refs.pagination;
if (!ul) return;
ul.innerHTML = '';
const mk = (label, page, disabled=false, active=false) => {
const li = document.createElement('li');
li.className = `page-item${disabled ? ' disabled' : ''}${active ? ' active' : ''}`;
const a = document.createElement('a');
a.className = 'page-link';
a.href = '#';
a.textContent = label;
a.onclick = (e) => {
e.preventDefault();
if (disabled || active) return;
this.page = page;
this.refresh();
};
li.appendChild(a);
return li;
};
// Prev
ul.appendChild(mk('«', Math.max(1, this.page - 1), this.page <= 1));
// Windowed page buttons
const maxButtons = 7;
let start = Math.max(1, this.page - Math.floor(maxButtons/2));
let end = Math.min(this.pages || 1, start + maxButtons - 1);
start = Math.max(1, Math.min(start, Math.max(1, end - maxButtons + 1)));
if (start > 1) ul.appendChild(mk('1', 1));
if (start > 2) ul.appendChild(mk('…', this.page, true));
for (let p = start; p <= end; p++) {
ul.appendChild(mk(String(p), p, false, p === this.page));
}
if (end < (this.pages || 1) - 1) ul.appendChild(mk('…', this.page, true));
if (end < (this.pages || 1)) ul.appendChild(mk(String(this.pages), this.pages));
// Next
ul.appendChild(mk('»', Math.min(this.pages || 1, this.page + 1), this.page >= (this.pages || 1)));
},
};
}