feat: Add pagination and filtering improvements to proposal votes
- Added pagination to the proposal votes table to improve usability with large datasets. - Implemented client-side filtering of votes by type and search terms, enhancing the user experience. - Improved the responsiveness and efficiency of the vote filtering and pagination.
This commit is contained in:
parent
3d8aca19cc
commit
97e7a04827
@ -275,7 +275,7 @@
|
|||||||
<th class="text-end pe-3">Date</th>
|
<th class="text-end pe-3">Date</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody id="votesTableBody">
|
||||||
{% if votes | length == 0 %}
|
{% if votes | length == 0 %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4" class="text-center py-4">
|
<td colspan="4" class="text-center py-4">
|
||||||
@ -329,6 +329,43 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination Controls -->
|
||||||
|
{% if votes | length > 10 %}
|
||||||
|
<div class="d-flex justify-content-between align-items-center p-3 border-top">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<label class="me-2 text-muted small">Rows per page:</label>
|
||||||
|
<select id="rowsPerPage" class="form-select form-select-sm" style="width: auto;">
|
||||||
|
<option value="10">10</option>
|
||||||
|
<option value="25">25</option>
|
||||||
|
<option value="50">50</option>
|
||||||
|
<option value="100">100</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<nav aria-label="Votes pagination">
|
||||||
|
<ul class="pagination pagination-sm mb-0" id="paginationControls">
|
||||||
|
<li class="page-item disabled" id="prevPage">
|
||||||
|
<a class="page-link" href="#" aria-label="Previous">
|
||||||
|
<span aria-hidden="true">«</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="page-item active"><a class="page-link" href="#">1</a></li>
|
||||||
|
<li class="page-item"><a class="page-link" href="#">2</a></li>
|
||||||
|
<li class="page-item"><a class="page-link" href="#">3</a></li>
|
||||||
|
<li class="page-item" id="nextPage">
|
||||||
|
<a class="page-link" href="#" aria-label="Next">
|
||||||
|
<span aria-hidden="true">»</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted small" id="paginationInfo">
|
||||||
|
Showing <span id="startRow">1</span>-<span id="endRow">10</span> of <span id="totalRows">{{ votes | length }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -362,27 +399,20 @@
|
|||||||
|
|
||||||
// Filter votes by type
|
// Filter votes by type
|
||||||
filterButtons.forEach(button => {
|
filterButtons.forEach(button => {
|
||||||
button.addEventListener('click', function () {
|
button.addEventListener('click', function() {
|
||||||
// Update active button
|
// Update active button
|
||||||
filterButtons.forEach(btn => btn.classList.remove('active'));
|
filterButtons.forEach(btn => btn.classList.remove('active'));
|
||||||
this.classList.add('active');
|
this.classList.add('active');
|
||||||
|
|
||||||
const filterType = this.getAttribute('data-filter');
|
// Reset to first page and update pagination
|
||||||
|
currentPage = 1;
|
||||||
voteRows.forEach(row => {
|
updatePagination();
|
||||||
if (filterType === 'all') {
|
|
||||||
row.style.display = '';
|
|
||||||
} else {
|
|
||||||
const voteType = row.getAttribute('data-vote-type');
|
|
||||||
row.style.display = (voteType === filterType) ? '' : 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Search functionality
|
// Search functionality
|
||||||
if (searchInput) {
|
if (searchInput) {
|
||||||
searchInput.addEventListener('input', function () {
|
searchInput.addEventListener('input', function() {
|
||||||
const searchTerm = this.value.toLowerCase();
|
const searchTerm = this.value.toLowerCase();
|
||||||
|
|
||||||
voteRows.forEach(row => {
|
voteRows.forEach(row => {
|
||||||
@ -395,9 +425,212 @@
|
|||||||
row.style.display = 'none';
|
row.style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Reset pagination after search
|
||||||
|
currentPage = 1;
|
||||||
|
updatePagination();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pagination functionality
|
||||||
|
const rowsPerPageSelect = document.getElementById('rowsPerPage');
|
||||||
|
const paginationControls = document.getElementById('paginationControls');
|
||||||
|
const votesTableBody = document.getElementById('votesTableBody');
|
||||||
|
const startRowElement = document.getElementById('startRow');
|
||||||
|
const endRowElement = document.getElementById('endRow');
|
||||||
|
const totalRowsElement = document.getElementById('totalRows');
|
||||||
|
const prevPageBtn = document.getElementById('prevPage');
|
||||||
|
const nextPageBtn = document.getElementById('nextPage');
|
||||||
|
|
||||||
|
let currentPage = 1;
|
||||||
|
let rowsPerPage = rowsPerPageSelect ? parseInt(rowsPerPageSelect.value) : 10;
|
||||||
|
|
||||||
|
// Function to update pagination display
|
||||||
|
function updatePagination() {
|
||||||
|
if (!paginationControls) return;
|
||||||
|
|
||||||
|
// Get all rows that match the current filter
|
||||||
|
const currentFilter = document.querySelector('[data-filter].active');
|
||||||
|
const filterType = currentFilter ? currentFilter.getAttribute('data-filter') : 'all';
|
||||||
|
|
||||||
|
// Get rows that match the current filter and search term
|
||||||
|
let filteredRows = Array.from(voteRows);
|
||||||
|
if (filterType !== 'all') {
|
||||||
|
filteredRows = filteredRows.filter(row => row.getAttribute('data-vote-type') === filterType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply search filter if there's a search term
|
||||||
|
const searchTerm = searchInput ? searchInput.value.toLowerCase() : '';
|
||||||
|
if (searchTerm) {
|
||||||
|
filteredRows = filteredRows.filter(row => {
|
||||||
|
const voterName = row.querySelector('td:first-child').textContent.toLowerCase();
|
||||||
|
const comment = row.querySelector('td:nth-child(3)').textContent.toLowerCase();
|
||||||
|
return voterName.includes(searchTerm) || comment.includes(searchTerm);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalRows = filteredRows.length;
|
||||||
|
|
||||||
|
// Calculate total pages
|
||||||
|
const totalPages = Math.max(1, Math.ceil(totalRows / rowsPerPage));
|
||||||
|
|
||||||
|
// Ensure current page is valid
|
||||||
|
if (currentPage > totalPages) {
|
||||||
|
currentPage = totalPages;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update pagination controls
|
||||||
|
if (paginationControls) {
|
||||||
|
// Clear existing page links (except prev/next)
|
||||||
|
const pageLinks = paginationControls.querySelectorAll('li:not(#prevPage):not(#nextPage)');
|
||||||
|
pageLinks.forEach(link => link.remove());
|
||||||
|
|
||||||
|
// Add new page links
|
||||||
|
const maxVisiblePages = 5;
|
||||||
|
let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2));
|
||||||
|
let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
|
||||||
|
|
||||||
|
// Adjust if we're near the end
|
||||||
|
if (endPage - startPage + 1 < maxVisiblePages && startPage > 1) {
|
||||||
|
startPage = Math.max(1, endPage - maxVisiblePages + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert page links before the next button
|
||||||
|
const nextPageElement = document.getElementById('nextPage');
|
||||||
|
for (let i = startPage; i <= endPage; i++) {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = `page-item ${i === currentPage ? 'active' : ''}`;
|
||||||
|
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.className = 'page-link';
|
||||||
|
a.href = '#';
|
||||||
|
a.textContent = i;
|
||||||
|
a.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
currentPage = i;
|
||||||
|
updatePagination();
|
||||||
|
});
|
||||||
|
|
||||||
|
li.appendChild(a);
|
||||||
|
paginationControls.insertBefore(li, nextPageElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update prev/next buttons
|
||||||
|
prevPageBtn.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
|
||||||
|
nextPageBtn.className = `page-item ${currentPage === totalPages ? 'disabled' : ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show current page
|
||||||
|
showCurrentPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to show current page
|
||||||
|
function showCurrentPage() {
|
||||||
|
if (!votesTableBody) return;
|
||||||
|
|
||||||
|
// Get all rows that match the current filter
|
||||||
|
const currentFilter = document.querySelector('[data-filter].active');
|
||||||
|
const filterType = currentFilter ? currentFilter.getAttribute('data-filter') : 'all';
|
||||||
|
|
||||||
|
// Get rows that match the current filter and search term
|
||||||
|
let filteredRows = Array.from(voteRows);
|
||||||
|
if (filterType !== 'all') {
|
||||||
|
filteredRows = filteredRows.filter(row => row.getAttribute('data-vote-type') === filterType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply search filter if there's a search term
|
||||||
|
const searchTerm = searchInput ? searchInput.value.toLowerCase() : '';
|
||||||
|
if (searchTerm) {
|
||||||
|
filteredRows = filteredRows.filter(row => {
|
||||||
|
const voterName = row.querySelector('td:first-child').textContent.toLowerCase();
|
||||||
|
const comment = row.querySelector('td:nth-child(3)').textContent.toLowerCase();
|
||||||
|
return voterName.includes(searchTerm) || comment.includes(searchTerm);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide all rows first
|
||||||
|
voteRows.forEach(row => row.style.display = 'none');
|
||||||
|
|
||||||
|
// Calculate pagination
|
||||||
|
const totalRows = filteredRows.length;
|
||||||
|
const totalPages = Math.max(1, Math.ceil(totalRows / rowsPerPage));
|
||||||
|
|
||||||
|
// Ensure current page is valid
|
||||||
|
if (currentPage > totalPages) {
|
||||||
|
currentPage = totalPages;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show only rows for current page
|
||||||
|
const start = (currentPage - 1) * rowsPerPage;
|
||||||
|
const end = start + rowsPerPage;
|
||||||
|
|
||||||
|
filteredRows.slice(start, end).forEach(row => row.style.display = '');
|
||||||
|
|
||||||
|
// Update pagination info
|
||||||
|
if (startRowElement && endRowElement && totalRowsElement) {
|
||||||
|
startRowElement.textContent = totalRows > 0 ? start + 1 : 0;
|
||||||
|
endRowElement.textContent = Math.min(end, totalRows);
|
||||||
|
totalRowsElement.textContent = totalRows;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event listeners for pagination
|
||||||
|
if (prevPageBtn) {
|
||||||
|
prevPageBtn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (currentPage > 1) {
|
||||||
|
currentPage--;
|
||||||
|
updatePagination();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextPageBtn) {
|
||||||
|
nextPageBtn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
// Get all rows that match the current filter
|
||||||
|
const currentFilter = document.querySelector('[data-filter].active');
|
||||||
|
const filterType = currentFilter ? currentFilter.getAttribute('data-filter') : 'all';
|
||||||
|
|
||||||
|
// Get rows that match the current filter and search term
|
||||||
|
let filteredRows = Array.from(voteRows);
|
||||||
|
if (filterType !== 'all') {
|
||||||
|
filteredRows = filteredRows.filter(row => row.getAttribute('data-vote-type') === filterType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply search filter if there's a search term
|
||||||
|
const searchTerm = searchInput ? searchInput.value.toLowerCase() : '';
|
||||||
|
if (searchTerm) {
|
||||||
|
filteredRows = filteredRows.filter(row => {
|
||||||
|
const voterName = row.querySelector('td:first-child').textContent.toLowerCase();
|
||||||
|
const comment = row.querySelector('td:nth-child(3)').textContent.toLowerCase();
|
||||||
|
return voterName.includes(searchTerm) || comment.includes(searchTerm);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalRows = filteredRows.length;
|
||||||
|
const totalPages = Math.max(1, Math.ceil(totalRows / rowsPerPage));
|
||||||
|
|
||||||
|
if (currentPage < totalPages) {
|
||||||
|
currentPage++;
|
||||||
|
updatePagination();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rowsPerPageSelect) {
|
||||||
|
rowsPerPageSelect.addEventListener('change', function() {
|
||||||
|
rowsPerPage = parseInt(this.value);
|
||||||
|
currentPage = 1; // Reset to first page
|
||||||
|
updatePagination();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize pagination
|
||||||
|
if (paginationControls) {
|
||||||
|
updatePagination();
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize tooltips for all elements with title attributes
|
// Initialize tooltips for all elements with title attributes
|
||||||
const tooltipElements = document.querySelectorAll('[title]');
|
const tooltipElements = document.querySelectorAll('[title]');
|
||||||
if (tooltipElements.length > 0) {
|
if (tooltipElements.length > 0) {
|
||||||
|
Loading…
Reference in New Issue
Block a user