let processingData = []; let resultsMap = null; let markers = []; let eventListenersInitialized = false; // Utility function to show status messages function showDataConvertStatus(message, type = 'info') { // Try to use the global showStatus from admin.js if available if (typeof window.showStatus === 'function') { return window.showStatus(message, type); } // Fallback to console console.log(`[${type.toUpperCase()}] ${message}`); // Try to display in status container if it exists const statusContainer = document.getElementById('status-container'); if (statusContainer) { const statusEl = document.createElement('div'); statusEl.className = `status-message status-${type}`; statusEl.textContent = message; statusContainer.appendChild(statusEl); // Auto-remove after 5 seconds setTimeout(() => { if (statusEl.parentNode) { statusEl.parentNode.removeChild(statusEl); } }, 5000); } } // Initialize when DOM is loaded document.addEventListener('DOMContentLoaded', () => { console.log('Data convert JS loaded'); // Don't auto-initialize, wait for section to be activated // Make setupDataConvertEventListeners available globally for admin.js window.setupDataConvertEventListeners = setupDataConvertEventListeners; }); function setupDataConvertEventListeners() { console.log('Setting up data convert event listeners...'); // Prevent duplicate event listeners if (eventListenersInitialized) { console.log('Event listeners already initialized, skipping...'); return; } const fileInput = document.getElementById('csv-file-input'); const browseBtn = document.getElementById('browse-btn'); const uploadArea = document.getElementById('upload-area'); const uploadForm = document.getElementById('csv-upload-form'); const clearBtn = document.getElementById('clear-upload-btn'); const saveResultsBtn = document.getElementById('save-results-btn'); const newUploadBtn = document.getElementById('new-upload-btn'); console.log('Elements found:', { fileInput: !!fileInput, browseBtn: !!browseBtn, uploadArea: !!uploadArea, uploadForm: !!uploadForm, clearBtn: !!clearBtn, saveResultsBtn: !!saveResultsBtn, newUploadBtn: !!newUploadBtn }); // File input change if (fileInput) { fileInput.addEventListener('change', handleFileSelect); } // Browse button if (browseBtn) { browseBtn.addEventListener('click', () => { console.log('Browse button clicked'); fileInput?.click(); }); } // Drag and drop if (uploadArea) { uploadArea.addEventListener('dragover', handleDragOver); uploadArea.addEventListener('dragleave', handleDragLeave); uploadArea.addEventListener('drop', handleDrop); uploadArea.addEventListener('click', () => fileInput?.click()); } // Form submission if (uploadForm) { uploadForm.addEventListener('submit', handleCSVUpload); } // Clear button if (clearBtn) { clearBtn.addEventListener('click', clearUpload); } // Save results button - ADD DATA TO MAP if (saveResultsBtn) { saveResultsBtn.addEventListener('click', saveGeocodedResults); console.log('Save results button event listener attached'); } // New upload button if (newUploadBtn) { newUploadBtn.addEventListener('click', resetToUpload); } // Mark as initialized eventListenersInitialized = true; console.log('Data convert event listeners initialized successfully'); } function handleFileSelect(e) { const file = e.target.files[0]; if (file) { displayFileInfo(file); } } function handleDragOver(e) { e.preventDefault(); e.currentTarget.classList.add('drag-over'); } function handleDragLeave(e) { e.preventDefault(); e.currentTarget.classList.remove('drag-over'); } function handleDrop(e) { e.preventDefault(); e.currentTarget.classList.remove('drag-over'); const files = e.dataTransfer.files; if (files.length > 0) { const file = files[0]; if (file.type === 'text/csv' || file.name.endsWith('.csv')) { document.getElementById('csv-file-input').files = files; displayFileInfo(file); } else { showDataConvertStatus('Please upload a CSV file', 'error'); } } } function displayFileInfo(file) { document.getElementById('file-info').style.display = 'block'; document.getElementById('file-name').textContent = file.name; document.getElementById('file-size').textContent = formatFileSize(file.size); document.getElementById('process-csv-btn').disabled = false; } function formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } async function handleCSVUpload(e) { e.preventDefault(); const fileInput = document.getElementById('csv-file-input'); const file = fileInput?.files[0]; if (!file) { showDataConvertStatus('Please select a CSV file', 'error'); return; } // Reset processing data processingData = []; // Show processing section const uploadSection = document.getElementById('upload-section'); const processingSection = document.getElementById('processing-section'); if (!uploadSection || !processingSection) { console.error('Required DOM elements not found'); showDataConvertStatus('Interface error: required elements not found', 'error'); return; } uploadSection.style.display = 'none'; processingSection.style.display = 'block'; // Initialize results map initializeResultsMap(); // Create form data const formData = new FormData(); formData.append('csvFile', file); try { // Send the file and handle SSE response const response = await fetch('/api/admin/data-convert/process-csv', { method: 'POST', body: formData }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } // Read the response as a stream for SSE const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; // Buffer to accumulate partial data while (true) { const { done, value } = await reader.read(); if (done) break; // Decode the chunk and add to buffer buffer += decoder.decode(value, { stream: true }); // Split buffer by lines and process complete lines const lines = buffer.split('\n'); // Keep the last potentially incomplete line in the buffer buffer = lines.pop() || ''; for (const line of lines) { if (line.startsWith('data: ')) { try { const jsonData = line.substring(6).trim(); // Remove 'data: ' prefix and trim if (jsonData && jsonData !== '') { const data = JSON.parse(jsonData); handleProcessingUpdate(data); } } catch (parseError) { console.warn('Failed to parse SSE data:', parseError); console.warn('Problematic line:', line); console.warn('JSON data:', line.substring(6)); } } else if (line.trim() === '') { // Empty line, ignore continue; } else if (line.trim() !== '' && !line.startsWith('event:') && !line.startsWith('id:')) { // Unexpected line format console.warn('Unexpected SSE line format:', line); } } } // Process any remaining data in buffer if (buffer.trim()) { const line = buffer; if (line.startsWith('data: ')) { try { const jsonData = line.substring(6).trim(); if (jsonData && jsonData !== '') { const data = JSON.parse(jsonData); handleProcessingUpdate(data); } } catch (parseError) { console.warn('Failed to parse final SSE data:', parseError); console.warn('Final buffer content:', buffer); } } } } catch (error) { console.error('CSV processing error:', error); showDataConvertStatus(error.message || 'Failed to process CSV', 'error'); resetToUpload(); } } // Enhanced processing update handler function handleProcessingUpdate(data) { console.log('Processing update:', data); // Validate data structure if (!data || typeof data !== 'object') { console.warn('Invalid data received:', data); return; } switch (data.type) { case 'start': updateProgress(0, data.total); document.getElementById('current-address').textContent = 'Starting geocoding process...'; break; case 'progress': updateProgress(data.current, data.total); document.getElementById('current-address').textContent = `Processing: ${data.address}`; break; case 'geocoded': // Mark as successful and add to processing data const successData = { ...data.data, geocode_success: true }; processingData.push(successData); addResultToTable(successData, 'success'); addMarkerToMap(successData); updateProgress(data.index + 1, data.total); break; case 'error': // Mark as failed and add to processing data const errorData = { ...data.data, geocode_success: false }; processingData.push(errorData); addResultToTable(errorData, 'error'); break; case 'complete': console.log('Received complete event:', data); onProcessingComplete(data); break; case 'fatal_error': showDataConvertStatus(data.message, 'error'); resetToUpload(); break; default: console.warn('Unknown data type received:', data.type); } } function updateProgress(current, total) { const percentage = (current / total) * 100; document.getElementById('progress-bar-fill').style.width = percentage + '%'; document.getElementById('progress-current').textContent = current; document.getElementById('progress-total').textContent = total; } function initializeResultsMap() { const mapContainer = document.getElementById('results-map'); if (!mapContainer) return; // Initialize map resultsMap = L.map('results-map').setView([53.5461, -113.4938], 11); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(resultsMap); document.getElementById('results-preview').style.display = 'block'; // Fix map sizing setTimeout(() => { resultsMap.invalidateSize(); }, 100); } function addResultToTable(data, status) { const tbody = document.getElementById('results-tbody'); const row = document.createElement('tr'); row.className = status === 'success' ? 'result-success' : 'result-error'; if (status === 'success') { row.innerHTML = `