forked from enviPath/enviPy
Rather than have a bunch of pull-requests that @jebus will have to merge shall we collect some of the fixes for the UI issues I found in here. - [x] #259 - [x] #260 - [x] #261 - [x] #262 - [x] #263 - [x] #264 - [x] #265 Co-authored-by: Tobias O <tobias.olenyi@envipath.com> Reviewed-on: enviPath/enviPy#266 Co-authored-by: Liam Brydon <lbry121@aucklanduni.ac.nz> Co-committed-by: Liam Brydon <lbry121@aucklanduni.ac.nz>
149 lines
4.0 KiB
JavaScript
149 lines
4.0 KiB
JavaScript
/**
|
|
* Alpine.js Pagination Component
|
|
*
|
|
* Provides client-side pagination for large lists.
|
|
*/
|
|
|
|
document.addEventListener('alpine:init', () => {
|
|
Alpine.data('remotePaginatedList', (options = {}) => ({
|
|
items: [],
|
|
currentPage: 1,
|
|
totalPages: 0,
|
|
totalItems: 0,
|
|
perPage: options.perPage || 50,
|
|
endpoint: options.endpoint || '',
|
|
isReviewed: options.isReviewed || false,
|
|
instanceId: options.instanceId || Math.random().toString(36).substring(2, 9),
|
|
isLoading: false,
|
|
error: null,
|
|
|
|
init() {
|
|
if (this.endpoint) {
|
|
this.fetchPage(1);
|
|
}
|
|
},
|
|
|
|
get paginatedItems() {
|
|
return this.items;
|
|
},
|
|
|
|
get showingStart() {
|
|
if (this.totalItems === 0) return 0;
|
|
return (this.currentPage - 1) * this.perPage + 1;
|
|
},
|
|
|
|
get showingEnd() {
|
|
if (this.totalItems === 0) return 0;
|
|
return Math.min((this.currentPage - 1) * this.perPage + this.items.length, this.totalItems);
|
|
},
|
|
|
|
async fetchPage(page) {
|
|
if (!this.endpoint) {
|
|
return;
|
|
}
|
|
|
|
this.isLoading = true;
|
|
this.error = null;
|
|
this.$dispatch('loading-start');
|
|
|
|
try {
|
|
const url = new URL(this.endpoint, window.location.origin);
|
|
// Preserve existing query parameters and add pagination params
|
|
url.searchParams.set('page', page.toString());
|
|
url.searchParams.set('page_size', this.perPage.toString());
|
|
|
|
const response = await fetch(url.toString(), {
|
|
headers: { Accept: 'application/json' },
|
|
credentials: 'same-origin'
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to load ${this.endpoint} (status ${response.status})`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
this.items = data.items || [];
|
|
this.totalItems = data.total_items || 0;
|
|
this.totalPages = data.total_pages || 0;
|
|
this.currentPage = data.page || page;
|
|
this.perPage = data.page_size || this.perPage;
|
|
|
|
// Dispatch event for parent components (e.g., tab count updates)
|
|
this.$dispatch('items-loaded', { totalItems: this.totalItems });
|
|
} catch (err) {
|
|
console.error(err);
|
|
this.error = `Unable to load ${this.endpoint}. Please try again.`;
|
|
} finally {
|
|
this.isLoading = false;
|
|
this.$dispatch('loading-end');
|
|
}
|
|
},
|
|
|
|
nextPage() {
|
|
if (this.currentPage < this.totalPages) {
|
|
this.fetchPage(this.currentPage + 1);
|
|
}
|
|
},
|
|
|
|
prevPage() {
|
|
if (this.currentPage > 1) {
|
|
this.fetchPage(this.currentPage - 1);
|
|
}
|
|
},
|
|
|
|
goToPage(page) {
|
|
if (page >= 1 && page <= this.totalPages) {
|
|
this.fetchPage(page);
|
|
}
|
|
},
|
|
|
|
get pageNumbers() {
|
|
const pages = [];
|
|
const total = this.totalPages;
|
|
const current = this.currentPage;
|
|
|
|
if (total === 0) {
|
|
return pages;
|
|
}
|
|
|
|
if (total <= 7) {
|
|
for (let i = 1; i <= total; i++) {
|
|
pages.push({ page: i, isEllipsis: false, key: `${this.instanceId}-page-${i}` });
|
|
}
|
|
} else {
|
|
pages.push({ page: 1, isEllipsis: false, key: `${this.instanceId}-page-1` });
|
|
|
|
let rangeStart;
|
|
let rangeEnd;
|
|
|
|
if (current <= 4) {
|
|
rangeStart = 2;
|
|
rangeEnd = 5;
|
|
} else if (current >= total - 3) {
|
|
rangeStart = total - 4;
|
|
rangeEnd = total - 1;
|
|
} else {
|
|
rangeStart = current - 1;
|
|
rangeEnd = current + 1;
|
|
}
|
|
|
|
if (rangeStart > 2) {
|
|
pages.push({ page: '...', isEllipsis: true, key: `${this.instanceId}-ellipsis-start` });
|
|
}
|
|
|
|
for (let i = rangeStart; i <= rangeEnd; i++) {
|
|
pages.push({ page: i, isEllipsis: false, key: `${this.instanceId}-page-${i}` });
|
|
}
|
|
|
|
if (rangeEnd < total - 1) {
|
|
pages.push({ page: '...', isEllipsis: true, key: `${this.instanceId}-ellipsis-end` });
|
|
}
|
|
|
|
pages.push({ page: total, isEllipsis: false, key: `${this.instanceId}-page-${total}` });
|
|
}
|
|
|
|
return pages;
|
|
}
|
|
}));
|
|
});
|