Add pagination to search page with configurable items per page
- Added pagination state (currentPage, pageSize) to search.js - Implemented getPaginatedResults() function to slice filtered results - Added getTotalPages() and helper functions for pagination math - Added pagination UI controls to search.html: * Items per page dropdown (15, 30, 60 options) * Previous/Next navigation buttons * Page info display (Page X of Y) * Result summary showing item range - Integrated pagination with search filtering: * Resets to page 1 on new search * Disables nav buttons at boundaries * Updates page info after each action - Added comprehensive CSS styling for responsive pagination controls - Updated README with pagination features and usage Features: ✓ Filter results first, then paginate ✓ Smart button disabling at boundaries ✓ Auto-reset to page 1 on search ✓ Dynamic page count updates ✓ Responsive design on mobile devices Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
152
search.html
152
search.html
@@ -128,6 +128,87 @@
|
||||
background-color: #ffebee;
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin: 30px 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.pagination-size {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.pagination-size label {
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.pagination-size select {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
background-color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pagination-size select:focus {
|
||||
outline: none;
|
||||
border-color: #4CAF50;
|
||||
}
|
||||
|
||||
.pagination-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.pagination-nav button {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.pagination-nav button:hover:not(:disabled) {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border-color: #4CAF50;
|
||||
}
|
||||
|
||||
.pagination-nav button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
min-width: 120px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.pagination-controls {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.pagination-size,
|
||||
.pagination-nav {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -212,13 +293,30 @@
|
||||
|
||||
<div id="results-container">
|
||||
<div class="results-info" id="results-info"></div>
|
||||
|
||||
<div class="pagination-controls">
|
||||
<div class="pagination-size">
|
||||
<label for="page-size-select">Items per page:</label>
|
||||
<select id="page-size-select">
|
||||
<option value="15">15</option>
|
||||
<option value="30">30</option>
|
||||
<option value="60">60</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="pagination-nav">
|
||||
<button type="button" id="prev-page-btn" class="btn">← Previous</button>
|
||||
<div class="pagination-info" id="page-info">Page 1 of 1</div>
|
||||
<button type="button" id="next-page-btn" class="btn">Next →</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="results-grid"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="navbar.js"></script>
|
||||
<script type="module">
|
||||
import { searchInventory, getLocations, formatDate, getExpiryStatus } from './search.js';
|
||||
import { searchInventory, getLocations, formatDate, getExpiryStatus, setPageSize, setCurrentPage, getPaginatedResults } from './search.js';
|
||||
import createItemComponent from './item-component.js';
|
||||
|
||||
const searchForm = document.getElementById('search-form');
|
||||
@@ -232,6 +330,13 @@
|
||||
const resetBtn = document.getElementById('reset-btn');
|
||||
const resultsGrid = document.getElementById('results-grid');
|
||||
const resultsInfo = document.getElementById('results-info');
|
||||
const pageSizeSelect = document.getElementById('page-size-select');
|
||||
const prevPageBtn = document.getElementById('prev-page-btn');
|
||||
const nextPageBtn = document.getElementById('next-page-btn');
|
||||
const pageInfo = document.getElementById('page-info');
|
||||
|
||||
let lastSearchResults = [];
|
||||
let currentPage = 1;
|
||||
|
||||
// Populate location dropdown
|
||||
function populateLocations() {
|
||||
@@ -250,16 +355,22 @@
|
||||
return `<div class="item-expiry-badge ${badgeClass}">${formatDate(expiryDate)}</div>`;
|
||||
}
|
||||
|
||||
// Display search results
|
||||
// Display search results with pagination
|
||||
function displayResults(results) {
|
||||
lastSearchResults = results;
|
||||
const paginationData = getPaginatedResults(results);
|
||||
|
||||
resultsGrid.innerHTML = '';
|
||||
|
||||
if (results.length === 0) {
|
||||
resultsGrid.innerHTML = '<div class="no-results">No items found matching your search criteria.</div>';
|
||||
resultsInfo.textContent = 'No results';
|
||||
pageInfo.textContent = 'Page 1 of 1';
|
||||
prevPageBtn.disabled = true;
|
||||
nextPageBtn.disabled = true;
|
||||
} else {
|
||||
resultsGrid.className = 'item-component-grid';
|
||||
results.forEach(item => {
|
||||
paginationData.items.forEach(item => {
|
||||
const comp = createItemComponent({
|
||||
imgSrc: item.img,
|
||||
imgAlt: item.name,
|
||||
@@ -269,14 +380,18 @@
|
||||
editable: false
|
||||
});
|
||||
|
||||
// Add expiry badge
|
||||
const badge = document.createElement('div');
|
||||
badge.innerHTML = getExpiryBadgeHtml(item.expiryDate);
|
||||
comp.appendChild(badge.firstChild);
|
||||
|
||||
resultsGrid.appendChild(comp);
|
||||
});
|
||||
resultsInfo.textContent = `Found ${results.length} item${results.length !== 1 ? 's' : ''}`;
|
||||
|
||||
resultsInfo.textContent = `Found ${results.length} item${results.length !== 1 ? 's' : ''} (showing ${paginationData.startIndex}-${paginationData.endIndex})`;
|
||||
pageInfo.textContent = `Page ${paginationData.currentPage} of ${paginationData.totalPages}`;
|
||||
|
||||
prevPageBtn.disabled = paginationData.currentPage === 1;
|
||||
nextPageBtn.disabled = paginationData.currentPage === paginationData.totalPages;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,12 +404,13 @@
|
||||
const minExpiry = minExpiryDateInput.value;
|
||||
const maxExpiry = maxExpiryDateInput.value;
|
||||
|
||||
// Validate date range
|
||||
if (minExpiry && maxExpiry && minExpiry > maxExpiry) {
|
||||
alert('Start date cannot be after end date');
|
||||
return;
|
||||
}
|
||||
|
||||
currentPage = 1;
|
||||
setCurrentPage(1);
|
||||
const results = searchInventory(searchName, location, minQty, maxQty, minExpiry, maxExpiry);
|
||||
displayResults(results);
|
||||
}
|
||||
@@ -309,11 +425,35 @@
|
||||
maxExpiryDateInput.value = '';
|
||||
resultsGrid.innerHTML = '';
|
||||
resultsInfo.textContent = '';
|
||||
currentPage = 1;
|
||||
setCurrentPage(1);
|
||||
pageInfo.textContent = 'Page 1 of 1';
|
||||
pageSizeSelect.value = '15';
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
searchBtn.addEventListener('click', performSearch);
|
||||
resetBtn.addEventListener('click', resetForm);
|
||||
|
||||
// Pagination event listeners
|
||||
pageSizeSelect.addEventListener('change', (e) => {
|
||||
currentPage = 1;
|
||||
setPageSize(parseInt(e.target.value));
|
||||
setCurrentPage(1);
|
||||
displayResults(lastSearchResults);
|
||||
});
|
||||
|
||||
prevPageBtn.addEventListener('click', () => {
|
||||
currentPage = Math.max(1, currentPage - 1);
|
||||
setCurrentPage(currentPage);
|
||||
displayResults(lastSearchResults);
|
||||
});
|
||||
|
||||
nextPageBtn.addEventListener('click', () => {
|
||||
currentPage++;
|
||||
setCurrentPage(currentPage);
|
||||
displayResults(lastSearchResults);
|
||||
});
|
||||
|
||||
// Allow Enter key to search
|
||||
searchNameInput.addEventListener('keypress', (e) => {
|
||||
|
||||
Reference in New Issue
Block a user