Cuts bug fixes and updates

This commit is contained in:
admin 2025-09-10 19:20:03 -06:00
parent f44cb35253
commit ebf9ff23ab
4 changed files with 275 additions and 8 deletions

View File

@ -19,7 +19,8 @@ class CutsController {
// For NocoDB v2 API, we need to get all records and filter in memory // For NocoDB v2 API, we need to get all records and filter in memory
// since the where clause syntax may be different // 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 config.nocodb.cutsSheetId
); );

View File

@ -791,10 +791,19 @@
<option value="Neighborhood">Neighborhood</option> <option value="Neighborhood">Neighborhood</option>
<option value="District">District</option> <option value="District">District</option>
</select> </select>
<select id="cuts-per-page" class="form-control">
<option value="5" selected>5 per page</option>
<option value="10">10 per page</option>
<option value="20">20 per page</option>
<option value="50">50 per page</option>
<option value="100">100 per page</option>
</select>
</div> </div>
<div id="cuts-list" class="cuts-list"> <div id="cuts-list" class="cuts-list">
<!-- Cuts will be populated here --> <!-- Cuts will be populated here -->
</div> </div>
<!-- Pagination Controls (will be dynamically created after cuts-list) -->
<div id="cuts-pagination" class="cuts-pagination" style="display: none;"></div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -516,3 +516,91 @@
display: flex; display: flex;
gap: 10px; 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;
}

View File

@ -23,6 +23,11 @@ class AdminCutsManager {
// Location markers for map display // Location markers for map display
this.locationMarkers = null; this.locationMarkers = null;
// Pagination properties
this.currentPage = 1;
this.itemsPerPage = 5;
this.totalPages = 1;
// Bind event handler once to avoid issues with removing listeners // Bind event handler once to avoid issues with removing listeners
this.boundHandleCutActionClick = this.handleCutActionClick.bind(this); this.boundHandleCutActionClick = this.handleCutActionClick.bind(this);
} }
@ -354,6 +359,16 @@ class AdminCutsManager {
categoryFilter.addEventListener('change', () => this.filterCuts()); 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 // Add drawing toolbar button handlers
const finishDrawingBtn = document.getElementById('finish-cut-btn'); const finishDrawingBtn = document.getElementById('finish-cut-btn');
if (finishDrawingBtn) { if (finishDrawingBtn) {
@ -1036,6 +1051,12 @@ class AdminCutsManager {
this.allCuts = data.list || []; this.allCuts = data.list || [];
this.filteredCuts = [...this.allCuts]; 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(); this.renderCutsList();
} catch (error) { } catch (error) {
@ -1056,11 +1077,31 @@ class AdminCutsManager {
if (this.filteredCuts.length === 0) { if (this.filteredCuts.length === 0) {
this.cutsList.innerHTML = '<p class="no-data">No cuts found</p>'; this.cutsList.innerHTML = '<p class="no-data">No cuts found</p>';
this.renderPagination(0);
return; return;
} }
const html = this.filteredCuts.map(cut => this.renderCutItem(cut)).join(''); // Calculate pagination
this.cutsList.innerHTML = html; 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 = `
<div class="cuts-list-header">
<div class="cuts-count">
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)` : ''}
</div>
</div>
`;
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 // Add event delegation for cut action buttons
this.cutsList.addEventListener('click', this.boundHandleCutActionClick); this.cutsList.addEventListener('click', this.boundHandleCutActionClick);
@ -1426,20 +1467,148 @@ class AdminCutsManager {
let filteredCuts = this.allCuts; let filteredCuts = this.allCuts;
if (searchTerm) { if (searchTerm) {
filteredCuts = filteredCuts.filter(cut => filteredCuts = filteredCuts.filter(cut => {
cut.name.toLowerCase().includes(searchTerm) || // Handle different possible field names and null/undefined values
(cut.description && cut.description.toLowerCase().includes(searchTerm)) 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) { 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; this.filteredCuts = filteredCuts;
// Reset to first page when filtering
this.currentPage = 1;
this.renderCutsList(); 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 = '<div class="pagination-controls">';
// Previous button
if (this.currentPage > 1) {
paginationHtml += `<button class="pagination-btn" data-page="${this.currentPage - 1}"> Previous</button>`;
}
// 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 += `<button class="pagination-btn" data-page="1">1</button>`;
if (startPage > 2) {
paginationHtml += '<span class="pagination-ellipsis">...</span>';
}
}
// Page numbers
for (let i = startPage; i <= endPage; i++) {
const isActive = i === this.currentPage ? 'active' : '';
paginationHtml += `<button class="pagination-btn ${isActive}" data-page="${i}">${i}</button>`;
}
// Last page and ellipsis
if (endPage < this.totalPages) {
if (endPage < this.totalPages - 1) {
paginationHtml += '<span class="pagination-ellipsis">...</span>';
}
paginationHtml += `<button class="pagination-btn" data-page="${this.totalPages}">${this.totalPages}</button>`;
}
// Next button
if (this.currentPage < this.totalPages) {
paginationHtml += `<button class="pagination-btn" data-page="${this.currentPage + 1}">Next </button>`;
}
paginationHtml += '</div>';
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() { exportCuts() {
const exportData = { const exportData = {
version: '1.0', version: '1.0',