diff --git a/map/app/controllers/cutsController.js b/map/app/controllers/cutsController.js
index d35ca77..54cdef2 100644
--- a/map/app/controllers/cutsController.js
+++ b/map/app/controllers/cutsController.js
@@ -19,7 +19,8 @@ class CutsController {
// For NocoDB v2 API, we need to get all records and filter in memory
// since the where clause syntax may be different
- const response = await nocodbService.getAll(
+ // Use paginated method to get ALL records (not just the default 25 limit)
+ const response = await nocodbService.getAllPaginated(
config.nocodb.cutsSheetId
);
diff --git a/map/app/public/admin.html b/map/app/public/admin.html
index 2f741a2..89cf465 100644
--- a/map/app/public/admin.html
+++ b/map/app/public/admin.html
@@ -791,10 +791,19 @@
Neighborhood
District
+
+ 5 per page
+ 10 per page
+ 20 per page
+ 50 per page
+ 100 per page
+
+
+
diff --git a/map/app/public/css/admin/cuts-shifts.css b/map/app/public/css/admin/cuts-shifts.css
index c0eea30..d0c4588 100644
--- a/map/app/public/css/admin/cuts-shifts.css
+++ b/map/app/public/css/admin/cuts-shifts.css
@@ -516,3 +516,91 @@
display: flex;
gap: 10px;
}
+
+/* Cuts Pagination Styles */
+.cuts-pagination {
+ margin-top: var(--padding-sm);
+ display: flex;
+ justify-content: center;
+ padding: var(--padding-sm) 0;
+ border-top: 1px solid var(--border-color);
+}
+
+.pagination-controls {
+ display: flex;
+ align-items: center;
+ gap: 0.25rem;
+ flex-wrap: wrap;
+ justify-content: center;
+}
+
+.pagination-btn {
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ color: var(--text-primary);
+ padding: 0.5rem 0.75rem;
+ border-radius: var(--border-radius);
+ font-size: 0.875rem;
+ cursor: pointer;
+ transition: all var(--transition-base);
+ min-width: 2.5rem;
+ text-align: center;
+ line-height: 1;
+}
+
+.pagination-btn:hover:not(.active):not(:disabled) {
+ background: var(--bg-hover);
+ border-color: var(--primary-color);
+ color: var(--primary-color);
+}
+
+.pagination-btn.active {
+ background: var(--primary-color);
+ border-color: var(--primary-color);
+ color: white;
+ font-weight: 600;
+}
+
+.pagination-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.pagination-ellipsis {
+ color: var(--text-secondary);
+ padding: 0.5rem 0.25rem;
+ font-size: 0.875rem;
+}
+
+/* Cuts list header with count information */
+.cuts-list-header {
+ padding: var(--padding-sm);
+ border-bottom: 1px solid var(--border-color);
+ background: var(--bg-tertiary);
+ border-radius: var(--border-radius) var(--border-radius) 0 0;
+}
+
+.cuts-count {
+ font-size: 0.875rem;
+ color: var(--text-secondary);
+ text-align: center;
+}
+
+/* Updated cuts filters to accommodate items per page selector */
+.cuts-filters {
+ display: flex;
+ gap: var(--padding-sm);
+ align-items: center;
+ flex-wrap: wrap;
+ margin-bottom: var(--padding-sm);
+}
+
+.cuts-filters .form-control {
+ flex: 1;
+ min-width: 120px;
+}
+
+.cuts-filters .form-control:last-child {
+ flex: 0 0 auto;
+ min-width: 100px;
+}
diff --git a/map/app/public/js/admin-cuts-manager.js b/map/app/public/js/admin-cuts-manager.js
index 779a946..63e5890 100644
--- a/map/app/public/js/admin-cuts-manager.js
+++ b/map/app/public/js/admin-cuts-manager.js
@@ -23,6 +23,11 @@ class AdminCutsManager {
// Location markers for map display
this.locationMarkers = null;
+ // Pagination properties
+ this.currentPage = 1;
+ this.itemsPerPage = 5;
+ this.totalPages = 1;
+
// Bind event handler once to avoid issues with removing listeners
this.boundHandleCutActionClick = this.handleCutActionClick.bind(this);
}
@@ -354,6 +359,16 @@ class AdminCutsManager {
categoryFilter.addEventListener('change', () => this.filterCuts());
}
+ // Set up items per page selector
+ const itemsPerPageSelect = document.getElementById('cuts-per-page');
+ if (itemsPerPageSelect) {
+ itemsPerPageSelect.addEventListener('change', (e) => {
+ this.itemsPerPage = parseInt(e.target.value);
+ this.currentPage = 1; // Reset to first page
+ this.renderCutsList();
+ });
+ }
+
// Add drawing toolbar button handlers
const finishDrawingBtn = document.getElementById('finish-cut-btn');
if (finishDrawingBtn) {
@@ -1036,6 +1051,12 @@ class AdminCutsManager {
this.allCuts = data.list || [];
this.filteredCuts = [...this.allCuts];
+
+ console.log(`Loaded ${this.allCuts.length} cuts from server`);
+ if (this.allCuts.length >= 100) {
+ console.warn('Large dataset detected. Consider implementing pagination or server-side filtering.');
+ }
+
this.renderCutsList();
} catch (error) {
@@ -1056,11 +1077,31 @@ class AdminCutsManager {
if (this.filteredCuts.length === 0) {
this.cutsList.innerHTML = 'No cuts found
';
+ this.renderPagination(0);
return;
}
- const html = this.filteredCuts.map(cut => this.renderCutItem(cut)).join('');
- this.cutsList.innerHTML = html;
+ // Calculate pagination
+ this.totalPages = Math.ceil(this.filteredCuts.length / this.itemsPerPage);
+ const startIndex = (this.currentPage - 1) * this.itemsPerPage;
+ const endIndex = startIndex + this.itemsPerPage;
+ const cutsToShow = this.filteredCuts.slice(startIndex, endIndex);
+
+ // Render header with count and pagination info
+ const headerHtml = `
+
+ `;
+
+ const cutsHtml = cutsToShow.map(cut => this.renderCutItem(cut)).join('');
+ this.cutsList.innerHTML = headerHtml + cutsHtml;
+
+ // Render pagination controls
+ this.renderPagination(this.filteredCuts.length);
// Add event delegation for cut action buttons
this.cutsList.addEventListener('click', this.boundHandleCutActionClick);
@@ -1426,20 +1467,148 @@ class AdminCutsManager {
let filteredCuts = this.allCuts;
if (searchTerm) {
- filteredCuts = filteredCuts.filter(cut =>
- cut.name.toLowerCase().includes(searchTerm) ||
- (cut.description && cut.description.toLowerCase().includes(searchTerm))
- );
+ filteredCuts = filteredCuts.filter(cut => {
+ // Handle different possible field names and null/undefined values
+ const cutName = cut.name || cut.Name || '';
+ const cutDescription = cut.description || cut.Description || '';
+
+ // Prioritize name matches - if name matches, return true immediately
+ if (cutName.toLowerCase().includes(searchTerm)) {
+ return true;
+ }
+
+ // Only check description if name doesn't match
+ return cutDescription.toLowerCase().includes(searchTerm);
+ });
+
+ // Sort results to prioritize name matches at the top
+ filteredCuts.sort((a, b) => {
+ const nameA = a.name || a.Name || '';
+ const nameB = b.name || b.Name || '';
+ const nameAMatches = nameA.toLowerCase().includes(searchTerm);
+ const nameBMatches = nameB.toLowerCase().includes(searchTerm);
+
+ // If both match by name or both don't match by name, maintain original order
+ if (nameAMatches === nameBMatches) {
+ return 0;
+ }
+
+ // Prioritize name matches (true comes before false)
+ return nameBMatches - nameAMatches;
+ });
}
if (categoryFilter) {
- filteredCuts = filteredCuts.filter(cut => cut.category === categoryFilter);
+ filteredCuts = filteredCuts.filter(cut => {
+ const cutCategory = cut.category || cut.Category || '';
+ return cutCategory === categoryFilter;
+ });
}
this.filteredCuts = filteredCuts;
+
+ // Reset to first page when filtering
+ this.currentPage = 1;
+
this.renderCutsList();
}
+ renderPagination(totalItems) {
+ const paginationContainer = document.getElementById('cuts-pagination') || this.createPaginationContainer();
+
+ if (totalItems <= this.itemsPerPage) {
+ paginationContainer.innerHTML = '';
+ paginationContainer.style.display = 'none';
+ return;
+ }
+
+ paginationContainer.style.display = 'block';
+
+ let paginationHtml = '';
+
+ paginationContainer.innerHTML = paginationHtml;
+
+ // Add click handlers for pagination
+ paginationContainer.addEventListener('click', (e) => {
+ if (e.target.classList.contains('pagination-btn') && e.target.dataset.page) {
+ this.goToPage(parseInt(e.target.dataset.page));
+ }
+ });
+ }
+
+ createPaginationContainer() {
+ let container = document.getElementById('cuts-pagination');
+ if (!container) {
+ container = document.createElement('div');
+ container.id = 'cuts-pagination';
+ container.className = 'cuts-pagination';
+
+ // Insert after cuts list
+ const cutsList = document.getElementById('cuts-list');
+ if (cutsList && cutsList.parentNode) {
+ cutsList.parentNode.insertBefore(container, cutsList.nextSibling);
+ }
+ }
+ return container;
+ }
+
+ goToPage(page) {
+ if (page < 1 || page > this.totalPages) return;
+
+ this.currentPage = page;
+ this.renderCutsList();
+
+ // Scroll to top of cuts list
+ const cutsList = document.getElementById('cuts-list');
+ if (cutsList) {
+ cutsList.scrollIntoView({ behavior: 'smooth', block: 'start' });
+ }
+ }
+
exportCuts() {
const exportData = {
version: '1.0',