/** * Admin Cuts Manager Module * Main class for managing cuts with form handling and UI interactions */ class AdminCutsManager { constructor() { this.cutsMap = null; this.cutDrawing = null; this.locationManager = null; this.printUtils = null; this.currentCutId = null; this.allCuts = []; this.filteredCuts = []; this.currentCutLayer = null; this.isInitialized = false; // Drawing and preview properties this.currentDrawingData = null; this.previewLayer = null; this.editingCutId = null; // 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); } async initialize() { // Prevent double initialization if (this.isInitialized) { console.log('AdminCutsManager already initialized'); // If already initialized but map needs refresh (e.g., section just became visible) this.refreshMapSize(); return; } console.log('Initializing admin cuts manager...'); // Check if cuts section is visible, if not defer map initialization const cutsSection = document.getElementById('cuts'); const isVisible = cutsSection && cutsSection.style.display !== 'none'; if (isVisible) { // Initialize map first if section is visible this.initializeMap(); } else { console.log('Cuts section not visible, deferring map initialization...'); // Set up observer to initialize map when section becomes visible this.setupVisibilityObserver(); } // Initialize form first this.initializeFormState(); // Initialize cuts list element this.cutsList = document.getElementById('cuts-list'); // Initialize drawing (only if map exists) if (this.cutsMap) { this.initializeDrawing(); } // Initialize location manager (only if map exists) if (this.cutsMap) { this.locationManager = new CutLocationManager(this.cutsMap, this); // Initialize print utilities this.printUtils = new CutPrintUtils(this.cutsMap, this, this.locationManager); } // Load existing cuts await this.loadCuts(); // Set initialized flag BEFORE logging to prevent re-entry this.isInitialized = true; console.log('Admin cuts manager initialized'); } initializeFormState() { console.log('Initializing form state...'); // Set up form elements this.form = document.getElementById('cut-form'); if (!this.form) { console.error('Cut form not found'); return; } // Keep form enabled at all times - users should be able to fill properties anytime // Only disable the save button until we have geometry const saveCutBtn = document.getElementById('save-cut-btn'); if (saveCutBtn) { saveCutBtn.disabled = true; } // Set up form submission this.form.addEventListener('submit', (e) => this.handleFormSubmit(e)); // Set up other form controls this.setupFormControls(); // Set up toolbar controls for drawing this.setupToolbarControls(); console.log('Form state initialized - form inputs enabled, save button disabled until geometry complete'); } // Method that accepts direct values to avoid DOM reading issues (kept for compatibility) updatePreviewStyleWithValues(colorOverride = null, opacityOverride = null) { const color = colorOverride || this.getCurrentColor(); const opacity = opacityOverride !== null ? opacityOverride : this.getCurrentOpacity(); console.log('updatePreviewStyleWithValues called with:', color, opacity); // Update drawing style if drawing is active if (this.cutDrawing) { this.cutDrawing.updateDrawingStyle(color, opacity); } } // Method to apply styles to all relevant layers applyStyleToLayers(color, opacity) { console.log('applyStyleToLayers called with color:', color, 'opacity:', opacity); // First, update the drawing tool's current style if drawing is active if (this.cutDrawing) { this.cutDrawing.updateDrawingStyle(color, opacity); } // Update any preview layer (GeoJSON) - this is the critical fix if (this.previewLayer) { this.updateLayerStyle(this.previewLayer, color, opacity); console.log('Preview GeoJSON layer style updated with opacity:', opacity); } // Update current cut layer if it exists - Enhanced handling if (this.currentCutLayer) { this.updateLayerStyle(this.currentCutLayer, color, opacity); console.log('Updated currentCutLayer with opacity:', opacity); } // If preview layer doesn't exist but we have drawing data, refresh the preview if (!this.previewLayer && this.currentDrawingData) { console.log('No preview layer found, refreshing preview with drawing data'); this.updateDrawingPreview(this.currentDrawingData); } } // New unified method to update any layer style updateLayerStyle(layer, color, opacity) { if (!layer) return; // Update layer options layer.options.fillOpacity = opacity; layer.options.fillColor = color; layer.options.color = color; // Apply new style layer.setStyle({ fillColor: color, color: color, fillOpacity: opacity, opacity: 0.8 // Border opacity }); // Force update on the path element(s) if (layer._path) { layer._path.style.setProperty('fill-opacity', opacity, 'important'); layer._path.style.setProperty('fill', color, 'important'); layer._path.style.setProperty('stroke', color, 'important'); // Force browser to recognize the change layer._path.style.opacity = ''; // Clear any overall opacity void layer._path.offsetHeight; // Force reflow } // Handle renderer sub-layers if (layer._renderer && layer._renderer._container) { const paths = layer._renderer._container.querySelectorAll('path'); paths.forEach(path => { path.style.setProperty('fill-opacity', opacity, 'important'); path.style.setProperty('fill', color, 'important'); path.style.setProperty('stroke', color, 'important'); // Force browser to recognize the change path.style.opacity = ''; // Clear any overall opacity void path.offsetHeight; // Force reflow }); } // Handle GeoJSON layers and layer groups if (layer.eachLayer) { // It's a layer group or GeoJSON - iterate through each feature layer.eachLayer((subLayer) => { if (subLayer.setStyle) { subLayer.setStyle({ color: color, fillColor: color, fillOpacity: opacity, opacity: 0.8 }); // Force inline styles on sub-layer paths if (subLayer._path) { subLayer._path.style.setProperty('fill-opacity', opacity, 'important'); subLayer._path.style.setProperty('fill', color, 'important'); subLayer._path.style.setProperty('stroke', color, 'important'); subLayer._path.style.opacity = ''; void subLayer._path.offsetHeight; } // Add CSS class to identify cut polygons const pathElement = subLayer.getElement(); if (pathElement) { pathElement.classList.add('cut-polygon'); pathElement.style.setProperty('fill-opacity', opacity, 'important'); console.log('Added cut-polygon class to sub-layer path element'); } // Force DOM update immediately this.forceLayerRedraw(subLayer); } }); // Also force a redraw of the entire layer group if (layer.redraw) { layer.redraw(); } } else if (layer.setStyle) { // It's a single layer (Leaflet Polygon) layer.setStyle({ fillColor: color, color: color, fillOpacity: opacity, opacity: 0.8 }); // Add CSS class to identify cut polygons const pathElement = layer.getElement(); if (pathElement) { pathElement.classList.add('cut-polygon'); console.log('Added cut-polygon class to single layer path element'); } // Force DOM update immediately this.forceLayerRedraw(layer); } } // New method to force layer redraw - addresses browser rendering issues forceLayerRedraw(layer) { if (layer._path) { // Direct SVG path manipulation for immediate visual update const path = layer._path; const targetOpacity = layer.options.fillOpacity; const targetColor = layer.options.fillColor || layer.options.color; console.log('forceLayerRedraw called:'); console.log(' - layer.options.fillOpacity:', targetOpacity); console.log(' - layer.options.fillColor:', targetColor); console.log(' - layer._path exists:', !!path); // Set the attribute directly on the SVG element path.setAttribute('fill-opacity', targetOpacity); path.setAttribute('fill', targetColor); // Also try setting as CSS style with important flag for better browser compatibility path.style.setProperty('fill-opacity', targetOpacity, 'important'); path.style.setProperty('fill', targetColor, 'important'); // Force browser reflow by temporarily changing a property const originalDisplay = path.style.display; path.style.display = 'none'; // Use requestAnimationFrame for better timing requestAnimationFrame(() => { path.style.display = originalDisplay; // Double-check the attribute was set const finalOpacity = path.getAttribute('fill-opacity'); const finalColor = path.getAttribute('fill'); const styleOpacity = path.style.fillOpacity; console.log('forceLayerRedraw completed:'); console.log(' - target opacity:', targetOpacity); console.log(' - target color:', targetColor); console.log(' - SVG attr opacity result:', finalOpacity); console.log(' - SVG attr color result:', finalColor); console.log(' - CSS style opacity result:', styleOpacity); // If attributes don't match, try one more time if (finalOpacity !== targetOpacity.toString()) { path.setAttribute('fill-opacity', targetOpacity); path.style.setProperty('fill-opacity', targetOpacity, 'important'); console.log(' - Re-applied fill-opacity attribute and style'); } }); } else { console.log('forceLayerRedraw: no _path found on layer'); } } setupFormControls() { // Set up start drawing button const startDrawingBtn = document.getElementById('start-drawing-btn'); if (startDrawingBtn) { // Remove any existing listeners first startDrawingBtn.removeEventListener('click', this.boundHandleStartDrawing); // Create bound method if it doesn't exist if (!this.boundHandleStartDrawing) { this.boundHandleStartDrawing = this.handleStartDrawing.bind(this); } startDrawingBtn.addEventListener('click', this.boundHandleStartDrawing); } // Set up reset form button const resetFormBtn = document.getElementById('reset-form-btn'); if (resetFormBtn) { resetFormBtn.addEventListener('click', () => this.resetForm()); } // Set up cancel edit button const cancelEditBtn = document.getElementById('cancel-edit-btn'); if (cancelEditBtn) { cancelEditBtn.addEventListener('click', () => this.cancelEdit()); } // Set up refresh cuts button const refreshCutsBtn = document.getElementById('refresh-cuts-btn'); if (refreshCutsBtn) { refreshCutsBtn.addEventListener('click', () => this.loadCuts()); } // Set up export button const exportCutsBtn = document.getElementById('export-cuts-btn'); if (exportCutsBtn) { exportCutsBtn.addEventListener('click', () => this.exportCuts()); } // Set up import file input const importCutsFile = document.getElementById('import-cuts-file'); if (importCutsFile) { importCutsFile.addEventListener('change', (e) => this.handleImportFile(e)); } // Set up search and filter const searchInput = document.getElementById('cuts-search'); if (searchInput) { searchInput.addEventListener('input', () => this.filterCuts()); } const categoryFilter = document.getElementById('cuts-category-filter'); if (categoryFilter) { 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) { finishDrawingBtn.addEventListener('click', () => { console.log('Finish button clicked'); if (this.cutDrawing) { console.log('Cut drawing exists, calling finishDrawing()'); console.log('Drawing state:', this.cutDrawing.getState()); this.cutDrawing.finishDrawing(); } else { console.error('Cut drawing not initialized'); } }); } else { console.error('Finish drawing button not found'); } const undoVertexBtn = document.getElementById('undo-vertex-btn'); if (undoVertexBtn) { undoVertexBtn.addEventListener('click', () => { if (this.cutDrawing) { this.cutDrawing.undoLastVertex(); this.updateDrawingUI(); } }); } const clearVerticesBtn = document.getElementById('clear-vertices-btn'); if (clearVerticesBtn) { clearVerticesBtn.addEventListener('click', () => { if (this.cutDrawing) { this.cutDrawing.clearVertices(); this.updateDrawingUI(); } }); } const cancelDrawingBtn = document.getElementById('cancel-cut-btn'); if (cancelDrawingBtn) { cancelDrawingBtn.addEventListener('click', () => { if (this.cutDrawing) { this.cutDrawing.cancelDrawing(); } }); } // Print cut view const printBtn = document.getElementById('print-cut-view'); if (printBtn) { printBtn.addEventListener('click', () => this.printUtils.printCutView()); } // Set up show locations toggle const showLocationsToggle = document.getElementById('show-locations-on-map'); if (showLocationsToggle) { showLocationsToggle.addEventListener('change', (e) => this.toggleLocationsOnMap(e.target.checked)); } } // Set up toolbar controls for real-time drawing feedback setupToolbarControls() { const colorPicker = document.getElementById('toolbar-color'); const opacitySlider = document.getElementById('toolbar-opacity'); const opacityDisplay = document.getElementById('toolbar-opacity-display'); console.log('Setting up toolbar controls...', { colorPicker: !!colorPicker, opacitySlider: !!opacitySlider, opacityDisplay: !!opacityDisplay }); if (colorPicker) { colorPicker.addEventListener('input', (e) => { const color = e.target.value; console.log('Toolbar color changed to:', color); // Update drawing style immediately if (this.cutDrawing) { const opacity = this.getCurrentOpacity(); console.log('Updating drawing style with color:', color, 'opacity:', opacity); this.cutDrawing.updateDrawingStyle(color, opacity); } }); } if (opacitySlider && opacityDisplay) { opacitySlider.addEventListener('input', (e) => { const opacity = parseFloat(e.target.value); const percentage = Math.round(opacity * 100); opacityDisplay.textContent = percentage + '%'; console.log('Toolbar opacity changed to:', opacity, 'percentage:', percentage); // Update drawing style immediately if (this.cutDrawing) { const color = this.getCurrentColor(); console.log('Updating drawing style with color:', color, 'opacity:', opacity); this.cutDrawing.updateDrawingStyle(color, opacity); } else { console.warn('cutDrawing instance not available'); } }); } console.log('Toolbar controls setup complete'); } // Helper methods to get current toolbar values getCurrentColor() { const colorPicker = document.getElementById('toolbar-color'); return colorPicker ? colorPicker.value : '#3388ff'; } getCurrentOpacity() { const opacitySlider = document.getElementById('toolbar-opacity'); return opacitySlider ? parseFloat(opacitySlider.value) : 0.3; } // Force update the drawing style - useful for debugging or manual refresh forceUpdateDrawingStyle() { if (this.cutDrawing) { const color = this.getCurrentColor(); const opacity = this.getCurrentOpacity(); console.log('Force updating drawing style with color:', color, 'opacity:', opacity); this.cutDrawing.updateDrawingStyle(color, opacity); } else { console.warn('cutDrawing instance not available for force update'); } } // Sync toolbar display values with actual slider values syncToolbarDisplayValues() { const opacitySlider = document.getElementById('toolbar-opacity'); const opacityDisplay = document.getElementById('toolbar-opacity-display'); if (opacitySlider && opacityDisplay) { const opacity = parseFloat(opacitySlider.value); const percentage = Math.round(opacity * 100); opacityDisplay.textContent = percentage + '%'; console.log('Synced opacity display to:', percentage + '%'); } // Also sync any existing preview layers with current toolbar values const color = this.getCurrentColor(); const opacity = this.getCurrentOpacity(); if (this.previewLayer) { console.log('Syncing preview layer with toolbar values:', color, opacity); this.updateLayerStyle(this.previewLayer, color, opacity); } if (this.currentCutLayer) { console.log('Syncing current cut layer with toolbar values:', color, opacity); this.updateLayerStyle(this.currentCutLayer, color, opacity); } } updateDrawingUI() { if (!this.cutDrawing) return; const state = this.cutDrawing.getState(); const vertexCount = document.getElementById('vertex-count'); const finishBtn = document.getElementById('finish-cut-btn'); const undoBtn = document.getElementById('undo-vertex-btn'); if (vertexCount) { vertexCount.textContent = `${state.vertexCount} points`; } if (finishBtn) { finishBtn.disabled = !state.canFinish; } if (undoBtn) { undoBtn.disabled = state.vertexCount === 0; } } async initializeMap() { const mapContainer = document.getElementById('cuts-map'); if (!mapContainer) { console.error('Cuts map container not found'); return; } // Check if map is already initialized if (this.cutsMap) { console.log('Cuts map already initialized'); return; } // Check if container already has a map instance if (mapContainer._leaflet_id) { console.log('Map container already has a Leaflet instance, cleaning up...'); // Try to find and remove existing map const existingMap = L.map.hasOwnProperty(mapContainer._leaflet_id) ? L.map[mapContainer._leaflet_id] : null; if (existingMap) { existingMap.remove(); } delete mapContainer._leaflet_id; } // Initialize map this.cutsMap = L.map('cuts-map').setView([53.5461, -113.4938], 11); // Add tile layer L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(this.cutsMap); console.log('Cuts map initialized'); } setupVisibilityObserver() { // Watch for when the cuts section becomes visible const cutsSection = document.getElementById('cuts'); if (!cutsSection) return; console.log('Setting up visibility observer for cuts section...'); const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'attributes' && mutation.attributeName === 'style') { const isVisible = cutsSection.style.display !== 'none'; if (isVisible && !this.cutsMap) { console.log('Cuts section became visible, initializing map...'); this.initializeMap(); // Initialize remaining components after map is ready setTimeout(() => { this.initializeDrawing(); this.locationManager = new CutLocationManager(this.cutsMap, this); this.printUtils = new CutPrintUtils(this.cutsMap, this, this.locationManager); }, 100); // Stop observing once initialized observer.disconnect(); } else if (isVisible && this.cutsMap) { // Map exists but might need refresh this.refreshMapSize(); } } }); }); observer.observe(cutsSection, { attributes: true, attributeFilter: ['style'] }); } refreshMapSize() { if (this.cutsMap) { console.log('Refreshing map size...'); // Force Leaflet to recalculate map size setTimeout(() => { this.cutsMap.invalidateSize(); this.cutsMap.invalidateSize(true); // Force refresh console.log('Map size refreshed'); }, 50); } } initializeDrawing() { this.cutDrawing = new CutDrawing(this.cutsMap); // Ensure clean state this.cutDrawing.isDrawing = false; // Bind drawing events this.cutDrawing.onFinish = (data) => this.handleDrawingFinished(data); this.cutDrawing.onCancel = () => this.handleDrawingCancelled(); this.cutDrawing.onUpdate = () => this.updateDrawingUI(); console.log('Drawing initialized, isDrawing:', this.cutDrawing.isDrawing); } handleStartDrawing() { console.log('handleStartDrawing called, current drawing state:', this.cutDrawing.isDrawing); // Prevent double-click issues by adding a small delay check if (this.handleStartDrawing._processing) { console.log('Already processing start drawing, ignoring...'); return; } this.handleStartDrawing._processing = true; try { if (this.cutDrawing.isDrawing) { console.log('Already drawing, canceling...'); this.cutDrawing.cancelDrawing(); return; } console.log('Starting new drawing...'); // Get current toolbar values instead of form values const color = this.getCurrentColor(); const opacity = this.getCurrentOpacity(); console.log('Starting new drawing with color:', color, 'opacity:', opacity); // Update the drawing tool with current style if (this.cutDrawing) { this.cutDrawing.currentColor = color; this.cutDrawing.currentOpacity = opacity; // Force update the drawing style immediately this.cutDrawing.updateDrawingStyle(color, opacity); } // Clear any existing preview layers if (this.previewLayer) { this.cutsMap.removeLayer(this.previewLayer); this.previewLayer = null; } // Clear any existing current cut layer if (this.currentCutLayer) { if (this.currentCutLayer.remove) { this.currentCutLayer.remove(); } else { this.cutsMap.removeLayer(this.currentCutLayer); } this.currentCutLayer = null; } // Clear form data this.currentDrawingData = null; document.getElementById('cut-geojson').value = ''; document.getElementById('cut-bounds').value = ''; // Clear any existing preview from the drawing tool if (this.cutDrawing && this.cutDrawing.clearPreview) { this.cutDrawing.clearPreview(); } // Update button text const startDrawingBtn = document.getElementById('start-drawing-btn'); if (startDrawingBtn) { startDrawingBtn.textContent = 'Cancel Drawing'; } this.cutDrawing.startDrawing( (data) => this.handleDrawingFinished(data), () => this.handleDrawingCancelled() ); // Show drawing toolbar const toolbar = document.getElementById('cut-drawing-toolbar'); if (toolbar) { toolbar.classList.add('active'); // Sync toolbar display values and apply to any existing layers this.syncToolbarDisplayValues(); } // Update UI this.updateDrawingUI(); } finally { // Clear the processing flag after a short delay setTimeout(() => { this.handleStartDrawing._processing = false; }, 100); } } handleDrawingFinished(drawingData) { console.log('handleDrawingFinished() called with data:', drawingData); // Store the drawing data this.currentDrawingData = drawingData; // Hide drawing toolbar document.getElementById('cut-drawing-toolbar').classList.remove('active'); // Store geojson and bounds in hidden form fields document.getElementById('cut-geojson').value = drawingData.geojson; document.getElementById('cut-bounds').value = drawingData.bounds; // Store the geojson in form dataset for form submission const form = document.getElementById('cut-form'); if (form) { form.dataset.geojson = drawingData.geojson; form.dataset.bounds = drawingData.bounds; } // Store the polygon reference for later use if (drawingData.polygon) { this.currentCutLayer = drawingData.polygon; } // Update form title const titleElement = document.getElementById('cut-form-title'); if (titleElement) { titleElement.textContent = 'Cut Properties - Ready to Save'; } // Enable the save button now that we have geometry const saveCutBtn = document.getElementById('save-cut-btn'); if (saveCutBtn) { saveCutBtn.disabled = false; } // Update start drawing button text const startDrawingBtn = document.getElementById('start-drawing-btn'); if (startDrawingBtn) { startDrawingBtn.textContent = 'Redraw Polygon'; } // Update preview with the drawn polygon using current toolbar values this.updateDrawingPreview(drawingData); // Force immediate style update with current toolbar values const color = this.getCurrentColor(); const opacity = this.getCurrentOpacity(); console.log('handleDrawingFinished: Applying toolbar values - color:', color, 'opacity:', opacity); // Apply to the preview layer immediately if (this.previewLayer) { this.updateLayerStyle(this.previewLayer, color, opacity); } // Also update the polygon from drawing data if it exists if (drawingData.polygon) { this.updateLayerStyle(drawingData.polygon, color, opacity); } this.showStatus('Cut drawing completed. Fill in the properties and save.', 'success'); } handleDrawingCancelled() { console.log('handleDrawingCancelled called'); const toolbar = document.getElementById('cut-drawing-toolbar'); if (toolbar) { toolbar.classList.remove('active'); } // Clear stored drawing data this.currentDrawingData = null; const geojsonField = document.getElementById('cut-geojson'); const boundsField = document.getElementById('cut-bounds'); if (geojsonField) geojsonField.value = ''; if (boundsField) boundsField.value = ''; // Clear form dataset const form = document.getElementById('cut-form'); if (form) { delete form.dataset.geojson; delete form.dataset.bounds; } const saveCutBtn = document.getElementById('save-cut-btn'); if (saveCutBtn) { saveCutBtn.disabled = true; } // Reset start drawing button text const startDrawingBtn = document.getElementById('start-drawing-btn'); if (startDrawingBtn) { startDrawingBtn.textContent = 'Start Drawing'; } console.log('Drawing cancelled, state reset'); this.showStatus('Cut drawing cancelled', 'info'); } async handleFormSubmit(event) { event.preventDefault(); console.log('Form submitted!'); const formData = new FormData(this.form); console.log('Form data entries:'); for (let [key, value] of formData.entries()) { console.log(`${key}: ${value}`); } const cutData = { name: formData.get('name'), description: formData.get('description'), color: this.getCurrentColor(), opacity: this.getCurrentOpacity(), category: formData.get('category'), is_public: formData.has('is_public'), is_official: formData.has('is_official') }; // Add the geojson and bounds from stored data if (this.currentDrawingData || event.target.dataset.geojson) { cutData.geojson = this.currentDrawingData?.geojson || event.target.dataset.geojson; cutData.bounds = this.currentDrawingData?.bounds || event.target.dataset.bounds; } else if (this.editingCutId) { // If editing and no new drawing, keep existing geometry const existingCut = this.allCuts.find(c => c.id === this.editingCutId); if (existingCut) { cutData.geojson = existingCut.geojson; cutData.bounds = existingCut.bounds; } } else { // Also try to get from hidden form fields as fallback cutData.geojson = formData.get('geojson'); cutData.bounds = formData.get('bounds'); } console.log('Cut data:', cutData); if (!cutData.name || !cutData.geojson) { this.showStatus('Name and geometry are required', 'error'); console.log('Validation failed - missing name or geojson'); return; } try { let result; if (this.editingCutId) { result = await this.updateCut(this.editingCutId, cutData); } else { result = await this.createCut(cutData); } if (result) { this.resetForm(); this.currentDrawingData = null; await this.loadCuts(); } } catch (error) { console.error('Error saving cut:', error); this.showStatus('Failed to save cut', 'error'); } } // Add a new method to update the drawing preview updateDrawingPreview(drawingData) { if (!drawingData || !drawingData.geojson) return; try { const geojson = JSON.parse(drawingData.geojson); // Remove any existing preview layer if (this.previewLayer) { this.cutsMap.removeLayer(this.previewLayer); this.previewLayer = null; } // Get current toolbar colors - this is the key fix const color = this.getCurrentColor(); const opacity = this.getCurrentOpacity(); console.log('updateDrawingPreview: Using toolbar values - color:', color, 'opacity:', opacity); // Create the GeoJSON layer with a static style object (not a function) // This allows setStyle() to work properly later this.previewLayer = L.geoJSON(geojson, { style: { color: color, fillColor: color, fillOpacity: opacity, weight: 2, opacity: 0.8, className: 'cut-polygon', dashArray: '3, 3' } }).addTo(this.cutsMap); // Add the cut-polygon CSS class to the path element and force inline styles if (this.previewLayer._layers) { Object.values(this.previewLayer._layers).forEach(layer => { if (layer._path) { layer._path.classList.add('cut-polygon'); // Force the fill-opacity inline style with important flag layer._path.style.setProperty('fill-opacity', opacity, 'important'); layer._path.style.setProperty('fill', color, 'important'); } }); } // Also check if we need to access sub-layers if (this.previewLayer._renderer && this.previewLayer._renderer._container) { const paths = this.previewLayer._renderer._container.querySelectorAll('path'); paths.forEach(path => { path.classList.add('cut-polygon'); // Force the fill-opacity inline style on all paths with important flag path.style.setProperty('fill-opacity', opacity, 'important'); path.style.setProperty('fill', color, 'important'); }); } // Force initial style application using our unified method this.updateLayerStyle(this.previewLayer, color, opacity); console.log('Drawing preview updated with opacity:', opacity); // Fit map to bounds if available if (drawingData.bounds) { const bounds = JSON.parse(drawingData.bounds); this.cutsMap.fitBounds(bounds, { padding: [20, 20] }); } } catch (error) { console.error('Error updating drawing preview:', error); } } // Method to refresh preview with current drawing data and form values refreshPreview() { if (this.currentDrawingData) { console.log('Refreshing preview with current form values'); this.updateDrawingPreview(this.currentDrawingData); } } async createCut(cutData) { try { const response = await fetch('/api/cuts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(cutData) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || `Failed to create cut: ${response.statusText}`); } const result = await response.json(); this.showStatus('Cut created successfully', 'success'); return result; } catch (error) { console.error('Error creating cut:', error); this.showStatus(error.message, 'error'); return null; } } async updateCut(id, cutData) { try { const response = await fetch(`/api/cuts/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(cutData) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || `Failed to update cut: ${response.statusText}`); } const result = await response.json(); this.showStatus('Cut updated successfully', 'success'); return result; } catch (error) { console.error('Error updating cut:', error); this.showStatus(error.message, 'error'); return null; } } async loadCuts() { try { const response = await fetch('/api/cuts', { credentials: 'include' }); if (!response.ok) { const errorText = await response.text(); console.error('Failed to load cuts:', response.status, errorText); throw new Error(`Failed to load cuts: ${response.statusText}`); } const data = await response.json(); // Handle case where cuts table might not exist or be empty if (!data.list) { console.log('No cuts data returned, initializing empty list'); data.list = []; } 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) { console.error('Error loading cuts:', error); this.showStatus('Failed to load cuts. Please check if the cuts table exists.', 'error'); // Initialize with empty array so the UI still works this.allCuts = []; this.filteredCuts = []; this.renderCutsList(); } } renderCutsList() { if (!this.cutsList) return; // Remove existing event listener to prevent duplicates this.cutsList.removeEventListener('click', this.boundHandleCutActionClick); if (this.filteredCuts.length === 0) { this.cutsList.innerHTML = '
No cuts found
'; this.renderPagination(0); return; } // 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 = `