// Global State let clientList = []; let rawGeoJSON = null; let prevGeoJSON = null; let generatedGeoJSON = null; let detectedClientCode = null; // DOM Elements const clientListInput = document.getElementById('clientListInput'); const rawGeoJSONInput = document.getElementById('rawGeoJSONInput'); const prevGeoJSONInput = document.getElementById('prevGeoJSONInput'); const clientSelect = document.getElementById('clientSelect'); const processBtn = document.getElementById('processBtn'); const downloadBtn = document.getElementById('downloadBtn'); const controlsSection = document.getElementById('controlsSection'); const diffViewer = document.getElementById('diffViewer'); const rawTableContainer = document.getElementById('rawTableContainer'); const editedTableContainer = document.getElementById('editedTableContainer'); const clearAllBtn = document.getElementById('clearAllBtn'); // Event Listeners clientListInput.addEventListener('change', handleClientListUpload); rawGeoJSONInput.addEventListener('change', handleRawGeoJSONUpload); prevGeoJSONInput.addEventListener('change', handlePrevGeoJSONUpload); processBtn.addEventListener('click', processGeoJSON); downloadBtn.addEventListener('click', downloadGeoJSON); clientSelect.addEventListener('change', () => { // If we have raw data, re-process or enable processing if (rawGeoJSON) { processBtn.disabled = false; } }); // Clear All Logic clearAllBtn.addEventListener('click', () => { if(confirm('Are you sure you want to clear all data and reset?')) { window.location.reload(); } }); // Helper: Attempt to match filename to client code function tryAutoSelectClient(filename) { if (!filename) return; // 1. Always clear the current selection and detected code first clientSelect.value = ""; detectedClientCode = null; // Matches strings starting with alphanumerics followed by an underscore OR a dot const match = filename.match(/^([A-Z0-9]+)[_.]/); if (match && match[1]) { const potentialCode = match[1]; console.log(`Detected potential client code from filename: ${potentialCode}`); // Save this in case the client list is uploaded AFTER the GeoJSON detectedClientCode = potentialCode; // 2. Try to select it in the dropdown immediately if the list exists const option = Array.from(clientSelect.options).find(opt => opt.value === potentialCode); if (option) { clientSelect.value = potentialCode; } else { // Optional: Log that a code was found in the file, but not in the dropdown list yet console.log("Client code found in filename but not in the current list."); } } } // Helper: Parse Excel function handleClientListUpload(e) { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(e) { const data = new Uint8Array(e.target.result); const workbook = XLSX.read(data, { type: 'array' }); let sheetName = workbook.SheetNames.find(n => n === 'Client List') || workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; const jsonData = XLSX.utils.sheet_to_json(worksheet); clientList = jsonData.map(row => ({ country: row['Country'], name: row['Name'], code: row['SC-client-Code'] })).filter(item => item.code); // Show count instead of filename document.getElementById('clientListStatus').textContent = `✓ ${clientList.length} clients uploaded`; populateClientDropdown(); controlsSection.classList.remove('hidden'); }; reader.readAsArrayBuffer(file); } function populateClientDropdown() { clientSelect.innerHTML = ''; clientList.forEach(client => { const option = document.createElement('option'); option.value = client.code; option.textContent = `${client.code} - ${client.name} (${client.country})`; clientSelect.appendChild(option); }); // Attempt auto-selection if code was detected previously (e.g., from raw upload before client list) if (detectedClientCode) { const option = Array.from(clientSelect.options).find(opt => opt.value === detectedClientCode); if (option) { clientSelect.value = detectedClientCode; } } } // Helper: Parse GeoJSON function handleRawGeoJSONUpload(e) { const file = e.target.files[0]; if (!file) return; // Show filename document.getElementById('rawGeoJSONStatus').textContent = `Uploaded: ${file.name}`; // Try to detect client code from this filename tryAutoSelectClient(file.name); const reader = new FileReader(); reader.onload = function(e) { try { rawGeoJSON = JSON.parse(e.target.result); renderTable(rawGeoJSON, rawTableContainer, false); // Clear the right column (edited view) when new raw file is uploaded editedTableContainer.innerHTML = ''; // Reset generated GeoJSON and disable download generatedGeoJSON = null; downloadBtn.disabled = true; diffViewer.classList.remove('hidden'); processBtn.disabled = false; } catch (err) { console.error('Error parsing GeoJSON:', err); alert('Error parsing Raw GeoJSON'); } }; reader.readAsText(file); } function handlePrevGeoJSONUpload(e) { const file = e.target.files[0]; if (!file) return; // Show filename document.getElementById('prevGeoJSONStatus').textContent = `Uploaded: ${file.name}`; // Try to detect client code from this filename tryAutoSelectClient(file.name); const reader = new FileReader(); reader.onload = function(e) { try { prevGeoJSON = JSON.parse(e.target.result); console.log("Previous GeoJSON loaded", prevGeoJSON); } catch (err) { console.error("Error parsing Previous GeoJSON", err); alert("Error parsing Previous GeoJSON"); } }; reader.readAsText(file); } // Core Logic: Generate IDs function processGeoJSON() { if (!rawGeoJSON) { alert("Please upload Raw GeoJSON first."); return; } // Validate Client Selection for Prefix const clientCode = clientSelect.value; if (!clientCode) { alert("Please select a Client first to generate the correct ID format."); return; } // Helper to extract number from ID // Handles simple "0001" or complex "CODE_0001" const extractIdNumber = (idStr) => { if (!idStr) return 0; // Split by underscore and take the last segment const parts = String(idStr).split('_'); const numPart = parts[parts.length - 1]; const num = parseInt(numPart, 10); return isNaN(num) ? 0 : num; }; // Determine max ID let maxId = 0; // 1. Scan Previous GeoJSON if (prevGeoJSON && prevGeoJSON.features) { prevGeoJSON.features.forEach(f => { if (f.properties && f.properties['sc_id']) { const num = extractIdNumber(f.properties['sc_id']); if (num > maxId) maxId = num; } }); } // 2. Scan Raw GeoJSON (avoid collisions) if (rawGeoJSON && rawGeoJSON.features) { rawGeoJSON.features.forEach(f => { if (f.properties && f.properties['sc_id']) { const num = extractIdNumber(f.properties['sc_id']); if (num > maxId) maxId = num; } }); } // Clone Raw GeoJSON to create Generated one generatedGeoJSON = JSON.parse(JSON.stringify(rawGeoJSON)); let currentId = maxId; generatedGeoJSON.features.forEach(f => { if (!f.properties) f.properties = {}; let existingId = f.properties['sc_id']; // Check if empty or missing if (existingId === undefined || existingId === null || existingId === "") { currentId++; // Format: CLIENTCODE_0001 f.properties['sc_id'] = `${clientCode}_${String(currentId).padStart(4, '0')}`; f.isNew = true; // Flag for UI } }); renderTable(generatedGeoJSON, editedTableContainer, true); downloadBtn.disabled = false; } function renderTable(geojson, container, isEdited) { if (!geojson || !geojson.features) return; container.innerHTML = ''; const table = document.createElement('table'); const thead = document.createElement('thead'); const trHead = document.createElement('tr'); ['Field', 'Subfield', 'SC_ID'].forEach(text => { const th = document.createElement('th'); th.textContent = text; trHead.appendChild(th); }); thead.appendChild(trHead); table.appendChild(thead); const tbody = document.createElement('tbody'); geojson.features.forEach(f => { const props = f.properties || {}; const field = props['field'] || ''; const subfield = props['subfield'] || ''; const scId = props['sc_id'] || ''; const tr = document.createElement('tr'); if (isEdited && f.isNew) { tr.className = 'diff-row new'; } const tdField = document.createElement('td'); tdField.textContent = field; tr.appendChild(tdField); const tdSubfield = document.createElement('td'); tdSubfield.textContent = subfield; tr.appendChild(tdSubfield); const tdScId = document.createElement('td'); tdScId.textContent = scId; tr.appendChild(tdScId); tbody.appendChild(tr); }); table.appendChild(tbody); container.appendChild(table); } function downloadGeoJSON() { if (!generatedGeoJSON) return; // Remove temporary flags const cleanGeoJSON = JSON.parse(JSON.stringify(generatedGeoJSON)); cleanGeoJSON.features.forEach(f => { delete f.isNew; }); const clientCode = clientSelect.value || "UNKNOWN"; const date = new Date(); const dateStr = date.toISOString().split('T')[0]; // YYYY-MM-DD const filename = `${clientCode}_${dateStr}.geojson`; const blob = new Blob([JSON.stringify(cleanGeoJSON, null, 2)], { type: 'application/geo+json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }