/** * Cut Manager Module * Handles cut CRUD operations and display functionality */ import { showStatus } from './utils.js'; export class CutManager { constructor() { this.cuts = []; this.currentCut = null; this.currentCutLayer = null; this.map = null; this.isInitialized = false; // Add support for multiple cuts this.displayedCuts = new Map(); // Track multiple displayed cuts this.cutLayers = new Map(); // Track cut layers by ID } /** * Initialize the cut manager */ async initialize(map) { this.map = map; this.isInitialized = true; // Load public cuts for display await this.loadPublicCuts(); console.log('Cut manager initialized'); } /** * Load all cuts (admin) or public cuts (users) */ async loadCuts(adminMode = false) { try { const endpoint = adminMode ? '/api/cuts' : '/api/cuts/public'; const response = await fetch(endpoint, { credentials: 'include' }); if (!response.ok) { throw new Error(`Failed to load cuts: ${response.statusText}`); } const data = await response.json(); this.cuts = data.list || []; console.log(`Loaded ${this.cuts.length} cuts`); return this.cuts; } catch (error) { console.error('Error loading cuts:', error); showStatus('Failed to load cuts', 'error'); return []; } } /** * Load public cuts for map display */ async loadPublicCuts() { return await this.loadCuts(false); } /** * Get single cut by ID */ async getCut(id) { try { const response = await fetch(`/api/cuts/${id}`, { credentials: 'include' }); if (!response.ok) { throw new Error(`Failed to load cut: ${response.statusText}`); } return await response.json(); } catch (error) { console.error('Error loading cut:', error); showStatus('Failed to load cut', 'error'); return null; } } /** * Create new cut */ 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(); showStatus('Cut created successfully', 'success'); // Reload cuts await this.loadCuts(true); return result; } catch (error) { console.error('Error creating cut:', error); showStatus(error.message, 'error'); return null; } } /** * Update existing cut */ 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(); showStatus('Cut updated successfully', 'success'); // Reload cuts await this.loadCuts(true); return result; } catch (error) { console.error('Error updating cut:', error); showStatus(error.message, 'error'); return null; } } /** * Delete cut */ async deleteCut(id) { try { const response = await fetch(`/api/cuts/${id}`, { method: 'DELETE', credentials: 'include' }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || `Failed to delete cut: ${response.statusText}`); } showStatus('Cut deleted successfully', 'success'); // If this was the currently displayed cut, hide it if (this.currentCut && this.currentCut.id === id) { this.hideCut(); } // Reload cuts await this.loadCuts(true); return true; } catch (error) { console.error('Error deleting cut:', error); showStatus(error.message, 'error'); return false; } } // ...existing code... displayCut(cutData, autoDisplayed = false) { if (!this.map) { console.error('Map not initialized'); return false; } // Normalize field names for consistent access const normalizedCut = { ...cutData, id: cutData.id || cutData.Id || cutData.ID, name: cutData.name || cutData.Name, description: cutData.description || cutData.Description, color: cutData.color || cutData.Color, opacity: cutData.opacity || cutData.Opacity, category: cutData.category || cutData.Category, geojson: cutData.geojson || cutData.GeoJSON || cutData['GeoJSON Data'], is_public: cutData.is_public || cutData['Public Visibility'], is_official: cutData.is_official || cutData['Official Cut'], autoDisplayed: autoDisplayed // Track if this was auto-displayed }; // Check if already displayed if (this.cutLayers.has(normalizedCut.id)) { console.log(`Cut already displayed: ${normalizedCut.name}`); return true; } if (!normalizedCut.geojson) { console.error('Cut has no GeoJSON data'); return false; } try { const geojsonData = typeof normalizedCut.geojson === 'string' ? JSON.parse(normalizedCut.geojson) : normalizedCut.geojson; // Parse opacity value - ensure it's a number between 0 and 1 let opacityValue = parseFloat(normalizedCut.opacity); // Validate opacity is within range if (isNaN(opacityValue) || opacityValue < 0 || opacityValue > 1) { opacityValue = 0.3; // Default fallback console.log(`Invalid opacity value (${normalizedCut.opacity}), using default: ${opacityValue}`); } const cutLayer = L.geoJSON(geojsonData, { style: { color: normalizedCut.color || '#3388ff', fillColor: normalizedCut.color || '#3388ff', fillOpacity: opacityValue, weight: 2, opacity: 0.8, // Stroke opacity - keeping this slightly transparent for better visibility className: 'cut-polygon' }, // Add onEachFeature to apply styles to each individual feature onEachFeature: function (feature, layer) { // Apply styles directly to the layer to ensure they override CSS if (layer.setStyle) { layer.setStyle({ fillOpacity: opacityValue, color: normalizedCut.color || '#3388ff', fillColor: normalizedCut.color || '#3388ff', weight: 2, opacity: 0.8 }); } // Add cut-polygon class to the path element if (layer._path) { layer._path.classList.add('cut-polygon'); } } }); // Add popup with cut info cutLayer.bindPopup(`
${normalizedCut.description}
` : ''} ${normalizedCut.category ? `Category: ${normalizedCut.category}
` : ''} ${normalizedCut.is_official ? 'Official Cut' : ''}