/** * 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 = `
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); } renderCutItem(cut) { console.log('Rendering cut item:', cut); const badges = []; const isPublic = cut.is_public || cut.Is_public || cut['Public Visibility']; const isOfficial = cut.is_official || cut.Is_official || cut['Official Cut']; if (isPublic) badges.push('Public'); else badges.push('Private'); if (isOfficial) badges.push('Official'); // Check different possible ID field names const cutId = cut.id || cut.Id || cut.ID || cut._id; const cutName = cut.name || cut.Name || 'Unknown'; const cutDescription = cut.description || cut.Description || ''; const cutCategory = cut.category || cut.Category || 'Custom'; const cutCreatedAt = cut.CreatedAt || cut.created_at || new Date().toISOString(); console.log('Cut ID found:', cutId, 'from cut object keys:', Object.keys(cut)); return `
${cutName} ${cutCategory}
${cutDescription ? `
${cutDescription}
` : ''}
${badges.join('')}
${new Date(cutCreatedAt).toLocaleDateString()}
`; } handleCutActionClick(event) { console.log('handleCutActionClick called', event); const button = event.target; console.log('Button:', button, 'Has data-action:', button.hasAttribute('data-action')); if (!button.hasAttribute('data-action')) return; event.preventDefault(); event.stopPropagation(); const action = button.getAttribute('data-action'); const cutId = button.getAttribute('data-cut-id'); console.log('Action:', action, 'Cut ID:', cutId); if (!cutId) return; switch (action) { case 'view': console.log('Calling viewCut'); this.viewCut(cutId); break; case 'edit': console.log('Calling editCut'); this.editCut(cutId); break; case 'duplicate': console.log('Calling duplicateCut'); this.duplicateCut(cutId); break; case 'delete': console.log('Calling deleteCut'); this.deleteCut(cutId); break; default: console.warn('Unknown cut action:', action); } } async viewCut(cutId) { console.log('viewCut called with ID:', cutId); const cut = this.allCuts.find(c => (c.id || c.Id || c.ID || c._id) == cutId); console.log('Found cut:', cut); if (!cut) return; this.displayCut(cut); this.locationManager.showLocationManagement(cutId); // Check if this cut should automatically show locations const shouldShowLocations = (cut.show_locations || cut.Show_locations || cut['Show Locations']) !== false; console.log('Cut show_locations setting:', shouldShowLocations); if (shouldShowLocations) { console.log('Auto-loading locations for cut view...'); try { await this.locationManager.loadCutLocations(); // Update the toggle button to reflect that locations are shown const toggleBtn = document.getElementById('toggle-location-visibility'); if (toggleBtn) { toggleBtn.textContent = 'Hide Locations'; toggleBtn.classList.add('active'); toggleBtn.classList.remove('inactive'); } } catch (error) { console.log('Failed to auto-load locations:', error); } } const cutName = cut.name || cut.Name || 'Unknown'; this.showStatus(`Viewing cut: ${cutName}`, 'info'); } displayCut(cutData) { if (this.currentCutLayer) { this.cutsMap.removeLayer(this.currentCutLayer); this.currentCutLayer = null; } if (!cutData) return false; // Get geojson from different possible field names const geojson = cutData.geojson || cutData.Geojson || cutData.GeoJSON || cutData['GeoJSON Data']; if (!geojson) { console.error('No geojson data found in cut:', cutData); return false; } try { const parsedGeojson = JSON.parse(geojson); // Get color and opacity from different possible field names const color = cutData.color || cutData.Color || '#3388ff'; const opacity = cutData.opacity || cutData.Opacity || 0.3; console.log('displayCut: Using color:', color, 'opacity:', opacity); // Create GeoJSON layer with static style object (not function) for proper setStyle() support this.currentCutLayer = L.geoJSON(parsedGeojson, { style: { color: color, fillColor: color, fillOpacity: opacity, weight: 2, opacity: 1.0 // Keep stroke solid } }); this.currentCutLayer.addTo(this.cutsMap); // Force apply the opacity using our enhanced styling approach this.updateLayerStyle(this.currentCutLayer, color, opacity); console.log('displayCut: Created currentCutLayer with opacity:', opacity); // Get bounds from different possible field names const bounds = cutData.bounds || cutData.Bounds; if (bounds) { try { const parsedBounds = JSON.parse(bounds); this.cutsMap.fitBounds(parsedBounds, { padding: [20, 20] }); } catch (boundsError) { this.cutsMap.fitBounds(this.currentCutLayer.getBounds(), { padding: [20, 20] }); } } else { this.cutsMap.fitBounds(this.currentCutLayer.getBounds(), { padding: [20, 20] }); } return true; } catch (error) { console.error('Error displaying cut:', error); this.showStatus('Failed to display cut', 'error'); return false; } } async editCut(cutId) { console.log('editCut called with ID:', cutId); const cut = this.allCuts.find(c => (c.id || c.Id || c.ID || c._id) == cutId); console.log('Found cut for editing:', cut); if (!cut) return; this.editingCutId = cutId; // Use both lowercase and uppercase field names document.getElementById('cut-name').value = cut.name || cut.Name || ''; document.getElementById('cut-description').value = cut.description || cut.Description || ''; // Set toolbar values (these are the ones we actually use now) const cutColor = cut.color || cut.Color || '#3388ff'; const cutOpacity = cut.opacity || cut.Opacity || 0.3; const toolbarColor = document.getElementById('toolbar-color'); const toolbarOpacity = document.getElementById('toolbar-opacity'); const toolbarOpacityDisplay = document.getElementById('toolbar-opacity-display'); if (toolbarColor) toolbarColor.value = cutColor; if (toolbarOpacity) toolbarOpacity.value = cutOpacity; if (toolbarOpacityDisplay) toolbarOpacityDisplay.textContent = Math.round(cutOpacity * 100) + '%'; document.getElementById('cut-category').value = cut.category || cut.Category || 'Custom'; document.getElementById('cut-public').checked = cut.is_public || cut.Is_public || cut['Public Visibility'] || false; document.getElementById('cut-official').checked = cut.is_official || cut.Is_official || cut['Official Cut'] || false; document.getElementById('cut-geojson').value = cut.geojson || cut.Geojson || cut.GeoJSON || cut['GeoJSON Data'] || ''; document.getElementById('cut-bounds').value = cut.bounds || cut.Bounds || ''; document.getElementById('cut-id').value = cut.id || cut.Id || cut.ID || cut._id; // Store the existing geometry in form dataset const form = document.getElementById('cut-form'); const geojsonData = cut.geojson || cut.Geojson || cut.GeoJSON || cut['GeoJSON Data']; const boundsData = cut.bounds || cut.Bounds; if (form && geojsonData) { form.dataset.geojson = geojsonData; form.dataset.bounds = boundsData || ''; } const cutName = cut.name || cut.Name || 'Unknown'; document.getElementById('cut-form-title').textContent = `Edit Cut: ${cutName}`; document.getElementById('save-cut-btn').textContent = 'Update Cut'; document.getElementById('save-cut-btn').disabled = false; document.getElementById('cancel-edit-btn').style.display = 'inline-block'; document.getElementById('start-drawing-btn').style.display = 'none'; this.displayCut(cut); this.showStatus(`Editing cut: ${cutName}`, 'info'); } async duplicateCut(cutId) { console.log('duplicateCut called with ID:', cutId); const cut = this.allCuts.find(c => (c.id || c.Id || c.ID || c._id) == cutId); console.log('Found cut for duplication:', cut); if (!cut) return; // Use both lowercase and uppercase field names const cutName = cut.name || cut.Name || 'Unknown'; const cutDescription = cut.description || cut.Description || ''; const cutColor = cut.color || cut.Color || '#3388ff'; const cutOpacity = cut.opacity || cut.Opacity || 0.3; const cutCategory = cut.category || cut.Category || 'Custom'; const cutGeojson = cut.geojson || cut.Geojson || cut.GeoJSON || cut['GeoJSON Data'] || ''; const cutBounds = cut.bounds || cut.Bounds || ''; const duplicateData = { name: `${cutName} (Copy)`, description: cutDescription, color: cutColor, opacity: cutOpacity, category: cutCategory, is_public: false, is_official: false, geojson: cutGeojson, bounds: cutBounds }; console.log('Duplicate data:', duplicateData); const result = await this.createCut(duplicateData); if (result) { await this.loadCuts(); this.showStatus(`Duplicated cut: ${cutName}`, 'success'); } } async deleteCut(cutId) { console.log('deleteCut called with ID:', cutId); const cut = this.allCuts.find(c => (c.id || c.Id || c.ID || c._id) == cutId); console.log('Found cut for deletion:', cut); if (!cut) return; const cutName = cut.name || cut.Name || 'Unknown'; if (!confirm(`Are you sure you want to delete the cut "${cutName}"? This action cannot be undone.`)) { return; } try { const response = await fetch(`/api/cuts/${cutId}`, { method: 'DELETE', credentials: 'include' }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || `Failed to delete cut: ${response.statusText}`); } this.showStatus('Cut deleted successfully', 'success'); if (this.currentCutLayer) { this.cutsMap.removeLayer(this.currentCutLayer); this.currentCutLayer = null; } await this.loadCuts(); } catch (error) { console.error('Error deleting cut:', error); this.showStatus(error.message, 'error'); } } cancelEdit() { this.resetForm(); // Hide the cancel button const cancelBtn = document.getElementById('cancel-edit-btn'); if (cancelBtn) { cancelBtn.style.display = 'none'; } } resetForm() { this.form.reset(); document.getElementById('cut-id').value = ''; document.getElementById('cut-geojson').value = ''; document.getElementById('cut-bounds').value = ''; // Reset toolbar values to defaults const toolbarColor = document.getElementById('toolbar-color'); const toolbarOpacity = document.getElementById('toolbar-opacity'); const toolbarOpacityDisplay = document.getElementById('toolbar-opacity-display'); if (toolbarColor) toolbarColor.value = '#3388ff'; if (toolbarOpacity) toolbarOpacity.value = '0.3'; if (toolbarOpacityDisplay) toolbarOpacityDisplay.textContent = '30%'; // Update UI document.getElementById('cut-form-title').textContent = 'Cut Properties'; document.getElementById('cancel-edit-btn').style.display = 'none'; // Only disable save button (form inputs stay enabled) const saveCutBtn = document.getElementById('save-cut-btn'); if (saveCutBtn) { saveCutBtn.disabled = true; } // Clear current cut this.currentCutId = null; // Reset location toggle to unchecked and hide locations const showLocationsToggle = document.getElementById('show-locations-on-map'); if (showLocationsToggle) { showLocationsToggle.checked = false; this.toggleLocationsOnMap(false); } // Clear any preview if (this.cutDrawing) { this.cutDrawing.clearPreview(); } // Hide location management panel this.locationManager.hideLocationManagement(); } filterCuts() { const searchTerm = document.getElementById('cuts-search').value.toLowerCase(); const categoryFilter = document.getElementById('cuts-category-filter').value; let filteredCuts = this.allCuts; if (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 => { 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', timestamp: new Date().toISOString(), cuts: this.allCuts.map(cut => ({ name: cut.name, description: cut.description, color: cut.color, opacity: cut.opacity, category: cut.category, is_official: cut.is_official, geojson: cut.geojson, bounds: cut.bounds })) }; const data = JSON.stringify(exportData, null, 2); const blob = new Blob([data], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `cuts-export-${new Date().toISOString().split('T')[0]}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); this.showStatus('Cuts exported successfully', 'success'); } // Toggle locations visibility on the cuts map async toggleLocationsOnMap(show) { if (!this.cutsMap) { console.warn('Cuts map not initialized'); return; } try { if (show) { // Load and display locations on the map const response = await fetch('/api/locations'); const data = await response.json(); if (data.success && data.locations) { // Remove existing location markers if any if (this.locationMarkers) { this.locationMarkers.clearLayers(); } else { this.locationMarkers = L.layerGroup().addTo(this.cutsMap); } // Add location markers to the map data.locations.forEach(location => { if (location.latitude && location.longitude) { // Handle multiple possible field names for support level const supportLevel = location.support_level || location['Support Level'] || location.supportLevel || location['support level'] || location.Support_Level || 'unknown'; // Debug logging for first few locations if (data.locations.indexOf(location) < 3) { console.log('Location debug:', { support_level: location.support_level, 'Support Level': location['Support Level'], supportLevel: location.supportLevel, 'support level': location['support level'], Support_Level: location.Support_Level, finalSupportLevel: supportLevel, allKeys: Object.keys(location) }); } const marker = L.circleMarker([location.latitude, location.longitude], { radius: 8, fillColor: this.getLocationColor(supportLevel), color: '#fff', weight: 2, opacity: 1, fillOpacity: 0.8 }); // Add popup with location info const firstName = location.first_name || location['First Name'] || ''; const lastName = location.last_name || location['Last Name'] || ''; const name = [firstName, lastName].filter(Boolean).join(' ') || 'Unknown'; const address = location.address || location.Address || ''; const email = location.email || location.Email || ''; const phone = location.phone || location.Phone || ''; const contact = [email, phone].filter(Boolean).join(', '); const hasSign = location.sign || location.Sign ? 'Yes' : 'No'; const signSize = location.sign_size || location['Sign Size'] || ''; const notes = location.notes || location.Notes || ''; const supportLevelText = this.getSupportLevelText(supportLevel); const popupContent = `
${name} ${supportLevelText}
📍 ${address || 'No address available'}
${contact ? `
📞 ${contact}
` : ''}
Lawn Sign: ${hasSign}${signSize ? ` (${signSize})` : ''}
${location.latitude && location.longitude ? `
Coordinates: ${parseFloat(location.latitude).toFixed(6)}, ${parseFloat(location.longitude).toFixed(6)}
` : ''}
${notes ? `
Notes: ${notes.length > 100 ? notes.substring(0, 100) + '...' : notes}
` : ''}
`; marker.bindPopup(popupContent, { maxWidth: 300, className: 'location-popup-container' }); this.locationMarkers.addLayer(marker); } }); console.log(`Added ${data.locations.length} location markers to cuts map`); } else { console.warn('No locations data received'); } } else { // Hide locations if (this.locationMarkers) { this.cutsMap.removeLayer(this.locationMarkers); this.locationMarkers = null; } console.log('Removed location markers from cuts map'); } } catch (error) { console.error('Error toggling locations on map:', error); this.showStatus('Failed to load locations', 'error'); } } // Helper method to get color based on support level getLocationColor(supportLevel) { switch (String(supportLevel)) { case '1': return '#28a745'; // Green - Strong Support case '2': return '#ffc107'; // Yellow - Lean Support case '3': return '#fd7e14'; // Orange - Lean Opposition case '4': return '#dc3545'; // Red - Strong Opposition default: return '#6c757d'; // Gray - Unknown } } // Helper method to get support level text getSupportLevelText(supportLevel) { switch (String(supportLevel)) { case '1': return 'Strong Support'; case '2': return 'Lean Support'; case '3': return 'Lean Opposition'; case '4': return 'Strong Opposition'; default: return 'Unknown'; } } async handleImportFile(event) { const file = event.target.files[0]; if (!file) return; try { const text = await file.text(); const data = JSON.parse(text); if (!data.cuts || !Array.isArray(data.cuts)) { throw new Error('Invalid import file format'); } let successCount = 0; let errorCount = 0; for (const cutData of data.cuts) { const result = await this.createCut(cutData); if (result) { successCount++; } else { errorCount++; } } await this.loadCuts(); if (successCount > 0) { this.showStatus(`Successfully imported ${successCount} cuts${errorCount > 0 ? `, ${errorCount} failed` : ''}`, 'success'); } else { this.showStatus('No cuts were imported', 'error'); } } catch (error) { console.error('Import error:', error); this.showStatus('Failed to import cuts: ' + error.message, 'error'); } event.target.value = ''; } // Debug method to check form state debugFormState() { console.log('=== Form State Debug ==='); const inputs = [ 'cut-name', 'cut-description', 'cut-color', 'cut-opacity', 'cut-category', 'cut-public', 'cut-official', 'save-cut-btn' ]; inputs.forEach(id => { const element = document.getElementById(id); if (element) { console.log(`${id}: disabled=${element.disabled}, value="${element.value || element.checked}"`); } else { console.log(`${id}: NOT FOUND`); } }); console.log(`currentDrawingData exists: ${!!this.currentDrawingData}`); console.log(`previewLayer exists: ${!!this.previewLayer}`); console.log('========================'); } // Add a debug method to check layer opacity state specifically debugOpacityState() { const colorElement = document.getElementById('cut-color'); const opacityElement = document.getElementById('cut-opacity'); console.log('=== DEBUG: Opacity State ==='); console.log('Color value:', colorElement?.value); console.log('Opacity value:', opacityElement?.value); console.log('Opacity parsed:', parseFloat(opacityElement?.value)); if (this.previewLayer) { console.log('Preview layer exists'); this.previewLayer.eachLayer((layer) => { console.log('Layer options fillOpacity:', layer.options.fillOpacity); if (layer._path) { const svgOpacity = layer._path.getAttribute('fill-opacity'); const computedStyle = window.getComputedStyle(layer._path); console.log('SVG fill-opacity attribute:', svgOpacity); console.log('Computed fill-opacity style:', computedStyle.fillOpacity); console.log('SVG fill color:', layer._path.getAttribute('fill')); } }); } else { console.log('No preview layer found'); } if (this.currentCutLayer) { console.log('Current cut layer exists'); if (this.currentCutLayer.eachLayer) { this.currentCutLayer.eachLayer((layer) => { console.log('Current layer options fillOpacity:', layer.options.fillOpacity); if (layer._path) { console.log('Current SVG fill-opacity:', layer._path.getAttribute('fill-opacity')); } }); } } else { console.log('No current cut layer found'); } console.log('========================'); } showStatus(message, type) { // Use existing admin notification system if available if (typeof showNotification === 'function') { showNotification(message, type); } else { console.log(`[${type.toUpperCase()}] ${message}`); } } } // Export the class if using modules, otherwise it's global if (typeof module !== 'undefined' && module.exports) { module.exports = AdminCutsManager; } else { window.AdminCutsManager = AdminCutsManager; }