From ebf9ff23abf185c3ac178b43b2ff2c2926d13a17 Mon Sep 17 00:00:00 2001 From: admin Date: Wed, 10 Sep 2025 19:20:03 -0600 Subject: [PATCH] Cuts bug fixes and updates --- map/app/controllers/cutsController.js | 3 +- map/app/public/admin.html | 9 ++ map/app/public/css/admin/cuts-shifts.css | 88 +++++++++++ map/app/public/js/admin-cuts-manager.js | 183 ++++++++++++++++++++++- 4 files changed, 275 insertions(+), 8 deletions(-) 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 @@ +
+ + 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 = ` +
+
+ Showing ${startIndex + 1}-${Math.min(endIndex, this.filteredCuts.length)} of ${this.filteredCuts.length} cuts + ${this.filteredCuts.length !== this.allCuts.length ? `(filtered from ${this.allCuts.length} total)` : ''} +
+
+ `; + + 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 = '
'; + + // Previous button + if (this.currentPage > 1) { + paginationHtml += ``; + } + + // Page numbers (show max 7 pages) + const maxVisiblePages = 7; + let startPage = Math.max(1, this.currentPage - Math.floor(maxVisiblePages / 2)); + let endPage = Math.min(this.totalPages, startPage + maxVisiblePages - 1); + + // Adjust if we're near the end + if (endPage - startPage < maxVisiblePages - 1) { + startPage = Math.max(1, endPage - maxVisiblePages + 1); + } + + // First page and ellipsis + if (startPage > 1) { + paginationHtml += ``; + if (startPage > 2) { + paginationHtml += '...'; + } + } + + // Page numbers + for (let i = startPage; i <= endPage; i++) { + const isActive = i === this.currentPage ? 'active' : ''; + paginationHtml += ``; + } + + // Last page and ellipsis + if (endPage < this.totalPages) { + if (endPage < this.totalPages - 1) { + paginationHtml += '...'; + } + paginationHtml += ``; + } + + // Next button + if (this.currentPage < this.totalPages) { + paginationHtml += ``; + } + + 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',