forked from enviPath/enviPy
This PR moves all the collection pages into the new UI in a rough push. I did not put the same amount of care into these as into search, index, and predict. ## Major changes - All modals are now migrated to a state based alpine.js implementation. - jQuery is no longer present in the base layout; ajax is replace by native fetch api - most of the pps.js is now obsolte (as I understand it; the code is not referenced any more @jebus please double check) - in-memory pagination for large result lists (set to 50; we can make that configurable later; performance degrades at around 1k) stukk a bit rough tracked in #235 ## Minor things - Sarch and index also use alpine now - The loading spinner is now CSS animated (not sure if it currently gets correctly called) ## Not done - Ihave not even cheked the admin pages. Not sure If these need migrations - The temporary migration pages still use the old template. Not sure what is supposed to happen with those? @jebus ## What I did to test - opend all pages in browse, and user ; plus all pages reachable from there. - Interacted and tested the functionality of each modal superfically with exception of the API key modal (no functional test). --- This PR is massive sorry for that; just did not want to push half-brokenn state. @jebus @liambrydon I would be glad if you could click around and try to break it :) Finally closes #133 Co-authored-by: Tim Lorsbach <tim@lorsba.ch> Reviewed-on: enviPath/enviPy#236 Co-authored-by: Tobias O <tobias.olenyi@envipath.com> Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
134 lines
3.7 KiB
JavaScript
134 lines
3.7 KiB
JavaScript
/**
|
|
* Alpine.js Pagination Component
|
|
*
|
|
* Provides client-side pagination for large lists.
|
|
*/
|
|
|
|
document.addEventListener('alpine:init', () => {
|
|
Alpine.data('paginatedList', (initialItems = [], options = {}) => ({
|
|
allItems: initialItems,
|
|
filteredItems: [],
|
|
currentPage: 1,
|
|
perPage: options.perPage || 50,
|
|
searchQuery: '',
|
|
isReviewed: options.isReviewed || false,
|
|
instanceId: options.instanceId || Math.random().toString(36).substring(2, 9),
|
|
|
|
init() {
|
|
this.filteredItems = this.allItems;
|
|
},
|
|
|
|
get totalPages() {
|
|
return Math.ceil(this.filteredItems.length / this.perPage);
|
|
},
|
|
|
|
get paginatedItems() {
|
|
const start = (this.currentPage - 1) * this.perPage;
|
|
const end = start + this.perPage;
|
|
return this.filteredItems.slice(start, end);
|
|
},
|
|
|
|
get totalItems() {
|
|
return this.filteredItems.length;
|
|
},
|
|
|
|
get showingStart() {
|
|
if (this.totalItems === 0) return 0;
|
|
return (this.currentPage - 1) * this.perPage + 1;
|
|
},
|
|
|
|
get showingEnd() {
|
|
return Math.min(this.currentPage * this.perPage, this.totalItems);
|
|
},
|
|
|
|
search(query) {
|
|
this.searchQuery = query.toLowerCase();
|
|
if (this.searchQuery === '') {
|
|
this.filteredItems = this.allItems;
|
|
} else {
|
|
this.filteredItems = this.allItems.filter(item =>
|
|
item.name.toLowerCase().includes(this.searchQuery)
|
|
);
|
|
}
|
|
this.currentPage = 1;
|
|
},
|
|
|
|
nextPage() {
|
|
if (this.currentPage < this.totalPages) {
|
|
this.currentPage++;
|
|
}
|
|
},
|
|
|
|
prevPage() {
|
|
if (this.currentPage > 1) {
|
|
this.currentPage--;
|
|
}
|
|
},
|
|
|
|
goToPage(page) {
|
|
if (page >= 1 && page <= this.totalPages) {
|
|
this.currentPage = page;
|
|
}
|
|
},
|
|
|
|
get pageNumbers() {
|
|
const pages = [];
|
|
const total = this.totalPages;
|
|
const current = this.currentPage;
|
|
|
|
// Handle empty case
|
|
if (total === 0) {
|
|
return pages;
|
|
}
|
|
|
|
if (total <= 7) {
|
|
// Show all pages if 7 or fewer
|
|
for (let i = 1; i <= total; i++) {
|
|
pages.push({ page: i, isEllipsis: false, key: `${this.instanceId}-page-${i}` });
|
|
}
|
|
} else {
|
|
// More than 7 pages - show first, last, and sliding window around current
|
|
// Always show first page
|
|
pages.push({ page: 1, isEllipsis: false, key: `${this.instanceId}-page-1` });
|
|
|
|
// Determine the start and end of the middle range
|
|
let rangeStart, rangeEnd;
|
|
|
|
if (current <= 4) {
|
|
// Near the beginning: show pages 2-5
|
|
rangeStart = 2;
|
|
rangeEnd = 5;
|
|
} else if (current >= total - 3) {
|
|
// Near the end: show last 4 pages before the last page
|
|
rangeStart = total - 4;
|
|
rangeEnd = total - 1;
|
|
} else {
|
|
// In the middle: show current page and one on each side
|
|
rangeStart = current - 1;
|
|
rangeEnd = current + 1;
|
|
}
|
|
|
|
// Add ellipsis before range if there's a gap
|
|
if (rangeStart > 2) {
|
|
pages.push({ page: '...', isEllipsis: true, key: `${this.instanceId}-ellipsis-start` });
|
|
}
|
|
|
|
// Add pages in the range
|
|
for (let i = rangeStart; i <= rangeEnd; i++) {
|
|
pages.push({ page: i, isEllipsis: false, key: `${this.instanceId}-page-${i}` });
|
|
}
|
|
|
|
// Add ellipsis after range if there's a gap
|
|
if (rangeEnd < total - 1) {
|
|
pages.push({ page: '...', isEllipsis: true, key: `${this.instanceId}-ellipsis-end` });
|
|
}
|
|
|
|
// Always show last page
|
|
pages.push({ page: total, isEllipsis: false, key: `${this.instanceId}-page-${total}` });
|
|
}
|
|
|
|
return pages;
|
|
}
|
|
}));
|
|
});
|