898 lines
32 KiB
JavaScript
898 lines
32 KiB
JavaScript
// Color scheme for countries
|
||
const countryColors = {
|
||
'South Africa': '#FF6B6B',
|
||
'Zimbabwe': '#4ECDC4',
|
||
'Eswatini': '#45B7D1',
|
||
'Mozambique': '#96CEB4',
|
||
'Malawi': '#FFEAA7',
|
||
'Zambia': '#DDA15E',
|
||
'Uganda': '#EE6C4D',
|
||
'Kenya': '#F1A208',
|
||
'Tanzania': '#A23B72',
|
||
'Rwanda': '#8E7DBE',
|
||
'Burundi': '#C9ADA7'
|
||
};
|
||
|
||
let map;
|
||
let mills = [];
|
||
let millMarkers = {};
|
||
let drawnItems = new Map();
|
||
let currentMode = 'view';
|
||
let currentEditingId = null;
|
||
let featureGroup;
|
||
let drawControl;
|
||
let osmLayer;
|
||
let satelliteLayer;
|
||
let currentMapType = 'osm';
|
||
let measureControl = null;
|
||
let filteredMills = [];
|
||
let currentFilters = {
|
||
search: '',
|
||
country: '',
|
||
minProduction: 0
|
||
};
|
||
let isMeasuring = false;
|
||
let measurePoints = [];
|
||
|
||
// Initialize map
|
||
function initMap() {
|
||
if (!document.getElementById('map')) {
|
||
console.error('Map container not found!');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
if (typeof L === 'undefined') {
|
||
throw new Error('Leaflet library (L) is not defined!');
|
||
}
|
||
map = L.map('map').setView([-20, 33], 5);
|
||
} catch (e) {
|
||
console.error('Error initializing map:', e);
|
||
return;
|
||
}
|
||
|
||
// OpenStreetMap layer
|
||
osmLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||
attribution: '© OpenStreetMap contributors',
|
||
maxZoom: 19
|
||
}).addTo(map);
|
||
|
||
// Satellite layer (Esri)
|
||
satelliteLayer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
|
||
attribution: '© Esri',
|
||
maxZoom: 19
|
||
});
|
||
|
||
// Initialize drawing
|
||
featureGroup = L.featureGroup().addTo(map);
|
||
|
||
drawControl = new L.Control.Draw({
|
||
edit: {
|
||
featureGroup: featureGroup,
|
||
poly: {
|
||
allowIntersection: false
|
||
}
|
||
},
|
||
draw: {
|
||
polygon: false,
|
||
polyline: false,
|
||
rectangle: false,
|
||
circle: false,
|
||
circlemarker: false,
|
||
marker: true
|
||
}
|
||
});
|
||
map.addControl(drawControl);
|
||
|
||
// Hide draw controls initially (show only in draw mode)
|
||
const drawToolbar = document.querySelector('.leaflet-draw-toolbar');
|
||
if (drawToolbar) {
|
||
drawToolbar.style.display = 'none';
|
||
}
|
||
|
||
// Handle drawn items
|
||
map.on(L.Draw.Event.CREATED, function(e) {
|
||
const layer = e.layer;
|
||
featureGroup.addLayer(layer);
|
||
|
||
if (layer instanceof L.Marker) {
|
||
// Auto-switch to draw mode if not already
|
||
if (currentMode !== 'draw') {
|
||
document.querySelector('[data-mode="draw"]').click();
|
||
}
|
||
|
||
const id = Date.now();
|
||
const latlng = layer.getLatLng();
|
||
|
||
// Round coordinates to 4 decimal places (~11m accuracy)
|
||
const roundedLat = Math.round(latlng.lat * 10000) / 10000;
|
||
const roundedLng = Math.round(latlng.lng * 10000) / 10000;
|
||
|
||
drawnItems.set(id, {
|
||
type: 'marker',
|
||
lat: roundedLat,
|
||
lng: roundedLng,
|
||
data: {}
|
||
});
|
||
layer.drawId = id;
|
||
updateDrawnItemsList();
|
||
showEditForm(id);
|
||
}
|
||
});
|
||
|
||
map.on(L.Draw.Event.DELETED, function(e) {
|
||
e.layers.eachLayer(function(layer) {
|
||
drawnItems.delete(layer.drawId);
|
||
});
|
||
updateDrawnItemsList();
|
||
});
|
||
|
||
// Satellite toggle
|
||
document.getElementById('mapToggleBtn').addEventListener('click', () => {
|
||
const btn = document.getElementById('mapToggleBtn');
|
||
if (currentMapType === 'osm') {
|
||
map.removeLayer(osmLayer);
|
||
map.addLayer(satelliteLayer);
|
||
currentMapType = 'satellite';
|
||
btn.textContent = '🗺️ Map';
|
||
} else {
|
||
map.removeLayer(satelliteLayer);
|
||
map.addLayer(osmLayer);
|
||
currentMapType = 'osm';
|
||
btn.textContent = '🛰️ Satellite';
|
||
}
|
||
});
|
||
|
||
// Measurement tool toggle
|
||
document.getElementById('measureBtn').addEventListener('click', () => {
|
||
const btn = document.getElementById('measureBtn');
|
||
const panel = document.getElementById('measurementPanel');
|
||
isMeasuring = !isMeasuring;
|
||
|
||
if (isMeasuring) {
|
||
measurePoints = [];
|
||
btn.textContent = '📏 Click points to measure';
|
||
btn.style.background = 'rgba(255,255,255,0.3)';
|
||
btn.style.fontWeight = '600';
|
||
panel.style.display = 'flex';
|
||
panel.style.flex = '1';
|
||
updateMeasurementPanel();
|
||
} else {
|
||
btn.textContent = '📏 Measure';
|
||
btn.style.background = 'rgba(255,255,255,0.1)';
|
||
btn.style.fontWeight = '400';
|
||
panel.style.display = 'none';
|
||
panel.style.flex = '';
|
||
// Clear measurement points from map
|
||
map.eachLayer(layer => {
|
||
if (layer.options && layer.options.className === 'measurement-point') {
|
||
map.removeLayer(layer);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
// Reset measurement button
|
||
document.getElementById('resetMeasurementBtn').addEventListener('click', () => {
|
||
measurePoints = [];
|
||
// Clear measurement points from map
|
||
map.eachLayer(layer => {
|
||
if (layer.options && layer.options.className === 'measurement-point') {
|
||
map.removeLayer(layer);
|
||
}
|
||
});
|
||
updateMeasurementPanel();
|
||
});
|
||
|
||
// Add map click listener for measurement
|
||
map.on('click', function(e) {
|
||
if (!isMeasuring) return;
|
||
|
||
const point = e.latlng;
|
||
measurePoints.push(point);
|
||
|
||
// Add marker
|
||
L.circleMarker(point, {
|
||
radius: 5,
|
||
fillColor: '#2596be',
|
||
color: 'white',
|
||
weight: 2,
|
||
opacity: 1,
|
||
fillOpacity: 0.8,
|
||
className: 'measurement-point'
|
||
}).addTo(map);
|
||
|
||
// If we have 2 or more points, show distance
|
||
if (measurePoints.length >= 2) {
|
||
const lastPoint = measurePoints[measurePoints.length - 2];
|
||
const currentPoint = measurePoints[measurePoints.length - 1];
|
||
const distance = lastPoint.distanceTo(currentPoint);
|
||
|
||
// Add polyline between points
|
||
L.polyline([lastPoint, currentPoint], {
|
||
color: '#2596be',
|
||
weight: 2,
|
||
opacity: 0.7,
|
||
dashArray: '5, 5',
|
||
className: 'measurement-point'
|
||
}).addTo(map);
|
||
}
|
||
|
||
// Update the measurement panel
|
||
updateMeasurementPanel();
|
||
});
|
||
|
||
// Load CSV data
|
||
loadMillsData();
|
||
|
||
// Attach mode button listeners
|
||
attachModeListeners();
|
||
|
||
// Attach filter listeners
|
||
attachFilterListeners();
|
||
}
|
||
|
||
// Attach filter event listeners
|
||
function attachFilterListeners() {
|
||
document.getElementById('searchInput').addEventListener('input', applyFilters);
|
||
document.getElementById('countryFilter').addEventListener('change', applyFilters);
|
||
document.getElementById('minProduction').addEventListener('change', applyFilters);
|
||
document.getElementById('resetFiltersBtn').addEventListener('click', resetFilters);
|
||
}
|
||
|
||
// Update measurement panel with current points and distances
|
||
function updateMeasurementPanel() {
|
||
const listDiv = document.getElementById('measurementList');
|
||
const totalDiv = document.getElementById('totalDistance');
|
||
|
||
if (measurePoints.length === 0) {
|
||
listDiv.innerHTML = '<p style="color: #999;">Click on the map to add measurement points</p>';
|
||
totalDiv.textContent = '0.00 km';
|
||
return;
|
||
}
|
||
|
||
let html = '';
|
||
let totalDistance = 0;
|
||
|
||
// Show each point
|
||
measurePoints.forEach((point, index) => {
|
||
let segmentDistance = '';
|
||
if (index > 0) {
|
||
const prevPoint = measurePoints[index - 1];
|
||
const dist = prevPoint.distanceTo(point);
|
||
const distKm = (dist / 1000).toFixed(2);
|
||
segmentDistance = ` - Segment: ${distKm} km`;
|
||
totalDistance += dist;
|
||
}
|
||
|
||
html += `<div style="padding: 8px; background: #f9f9f9; margin-bottom: 6px; border-radius: 3px; border-left: 3px solid #2596be;">
|
||
<strong>Point ${index + 1}</strong> (${point.lat.toFixed(4)}, ${point.lng.toFixed(4)})${segmentDistance}
|
||
</div>`;
|
||
});
|
||
|
||
listDiv.innerHTML = html;
|
||
totalDiv.textContent = (totalDistance / 1000).toFixed(2) + ' km';
|
||
}
|
||
|
||
// Load mills from CSV
|
||
async function loadMillsData() {
|
||
try {
|
||
const response = await fetch('sugar_cane_factories_africa.csv');
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! status: ${response.status}`);
|
||
}
|
||
const csvText = await response.text();
|
||
parseCSV(csvText);
|
||
console.log('Mills loaded:', mills.length, mills.slice(0, 2));
|
||
renderMills();
|
||
console.log('Mills rendered');
|
||
updateLegend();
|
||
console.log('Legend updated');
|
||
} catch (error) {
|
||
console.error('Error loading CSV:', error);
|
||
// Show a notification to user
|
||
const notification = document.createElement('div');
|
||
notification.style.cssText = 'position: fixed; top: 20px; right: 20px; background: #ff6b6b; color: white; padding: 15px 20px; border-radius: 5px; z-index: 9999;';
|
||
notification.textContent = '⚠️ Could not load mill data. Make sure sugar_cane_factories_africa.csv is in the same folder.';
|
||
document.body.appendChild(notification);
|
||
setTimeout(() => notification.remove(), 5000);
|
||
}
|
||
}
|
||
|
||
// Parse CSV
|
||
function parseCSV(csvText) {
|
||
const lines = csvText.trim().split('\n');
|
||
const headers = lines[0].split(',').map(h => h.trim());
|
||
|
||
mills = lines.slice(1).map(line => {
|
||
const values = parseCSVLine(line);
|
||
const row = {};
|
||
headers.forEach((header, idx) => {
|
||
row[header] = values[idx] ? values[idx].trim() : '';
|
||
});
|
||
return row;
|
||
}).filter(row => row.Latitude && row.Longitude);
|
||
}
|
||
|
||
// Parse CSV line handling quoted values
|
||
function parseCSVLine(line) {
|
||
const result = [];
|
||
let current = '';
|
||
let inQuotes = false;
|
||
|
||
for (let i = 0; i < line.length; i++) {
|
||
const char = line[i];
|
||
const nextChar = line[i + 1];
|
||
|
||
if (char === '"') {
|
||
if (inQuotes && nextChar === '"') {
|
||
current += '"';
|
||
i++;
|
||
} else {
|
||
inQuotes = !inQuotes;
|
||
}
|
||
} else if (char === ',' && !inQuotes) {
|
||
result.push(current);
|
||
current = '';
|
||
} else {
|
||
current += char;
|
||
}
|
||
}
|
||
result.push(current);
|
||
return result;
|
||
}
|
||
|
||
// Render mills on map
|
||
function renderMills() {
|
||
mills.forEach(mill => {
|
||
const lat = parseFloat(mill.Latitude);
|
||
const lng = parseFloat(mill.Longitude);
|
||
|
||
if (!isNaN(lat) && !isNaN(lng)) {
|
||
const production = parseFloat(mill['Annual Sugar Production (tons)']) || 0;
|
||
const size = getCircleSize(production);
|
||
const color = countryColors[mill.Country] || '#999';
|
||
|
||
const marker = L.circleMarker([lat, lng], {
|
||
radius: size,
|
||
fillColor: color,
|
||
color: 'white',
|
||
weight: 2,
|
||
opacity: 0.8,
|
||
fillOpacity: 0.7
|
||
}).addTo(map);
|
||
|
||
const popup = createPopup(mill);
|
||
marker.bindPopup(popup);
|
||
|
||
millMarkers[`${lat},${lng}`] = marker;
|
||
}
|
||
});
|
||
|
||
// Initialize filtered mills array
|
||
filteredMills = [...mills];
|
||
}
|
||
|
||
// Get circle size based on production
|
||
function getCircleSize(production) {
|
||
if (production > 150000) return 15;
|
||
if (production > 50000) return 10;
|
||
return 6;
|
||
}
|
||
|
||
// Create popup content
|
||
function createPopup(mill) {
|
||
const production = mill['Annual Sugar Production (tons)'] || 'N/A';
|
||
const capacity = mill['Crushing Capacity (tons/year)'] || 'N/A';
|
||
const year = mill['Data Year'] || '';
|
||
|
||
return `
|
||
<div style="font-size: 13px; width: 280px;">
|
||
<strong style="font-size: 14px; color: #333;">${mill['Mill/Factory']}</strong><br>
|
||
<small style="color: #666;">${mill['Country']} • ${mill['Province/Region']}</small>
|
||
<hr style="margin: 8px 0; border: none; border-top: 1px solid #eee;">
|
||
<table style="width: 100%; font-size: 12px;">
|
||
<tr>
|
||
<td style="font-weight: 600; color: #333;">Company:</td>
|
||
<td>${mill.Company || 'N/A'}</td>
|
||
</tr>
|
||
<tr>
|
||
<td style="font-weight: 600; color: #333;">Production:</td>
|
||
<td>${formatNumber(production)} tons/year</td>
|
||
</tr>
|
||
<tr>
|
||
<td style="font-weight: 600; color: #333;">Capacity:</td>
|
||
<td>${formatNumber(capacity)} tons/year</td>
|
||
</tr>
|
||
<tr>
|
||
<td style="font-weight: 600; color: #333;">Coordinates:</td>
|
||
<td>${parseFloat(mill.Latitude).toFixed(4)}, ${parseFloat(mill.Longitude).toFixed(4)}</td>
|
||
</tr>
|
||
<tr>
|
||
<td style="font-weight: 600; color: #333;">Data Year:</td>
|
||
<td>${year}</td>
|
||
</tr>
|
||
${mill.Notes ? `<tr><td colspan="2" style="padding-top: 8px; color: #555; font-style: italic;">"${mill.Notes}"</td></tr>` : ''}
|
||
</table>
|
||
<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #eee;">
|
||
<button onclick="showFeedbackModal('${mill['Mill/Factory']}', '${mill['Country']}', '${parseFloat(mill.Latitude).toFixed(4)}', '${parseFloat(mill.Longitude).toFixed(4)}')" style="width: 100%; padding: 8px; background: #2596be; color: white; border: none; border-radius: 4px; font-size: 12px; font-weight: 600; cursor: pointer;">💬 Send Feedback</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// Format numbers
|
||
function formatNumber(num) {
|
||
if (!num || num === 'N/A') return 'N/A';
|
||
const n = parseFloat(num);
|
||
if (isNaN(n)) return 'N/A';
|
||
return n.toLocaleString('en-US', { maximumFractionDigits: 0 });
|
||
}
|
||
|
||
// Update legend and populate country filter
|
||
function updateLegend() {
|
||
const countries = [...new Set(mills.map(m => m.Country))].sort();
|
||
const legendHTML = countries.map(country => {
|
||
const color = countryColors[country] || '#999';
|
||
const count = mills.filter(m => m.Country === country).length;
|
||
return `
|
||
<div class="country-item">
|
||
<div class="country-color" style="background: ${color};"></div>
|
||
<span>${country} (${count})</span>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
|
||
document.getElementById('legendContainer').innerHTML = legendHTML;
|
||
|
||
// Update country filter dropdown
|
||
const countryFilter = document.getElementById('countryFilter');
|
||
const currentValue = countryFilter.value;
|
||
countryFilter.innerHTML = '<option value="">All Countries</option>' +
|
||
countries.map(c => `<option value="${c}">${c}</option>`).join('');
|
||
countryFilter.value = currentValue;
|
||
}
|
||
|
||
// Apply filters to mills
|
||
function applyFilters() {
|
||
const search = document.getElementById('searchInput').value.toLowerCase();
|
||
const country = document.getElementById('countryFilter').value;
|
||
const minProduction = parseFloat(document.getElementById('minProduction').value) || 0;
|
||
|
||
console.log('Applying filters:', { search, country, minProduction, totalMills: mills.length });
|
||
|
||
currentFilters = { search, country, minProduction };
|
||
|
||
// Filter mills
|
||
filteredMills = mills.filter(mill => {
|
||
const matchesSearch = !search ||
|
||
mill['Mill/Factory'].toLowerCase().includes(search) ||
|
||
mill['Company'].toLowerCase().includes(search);
|
||
|
||
const matchesCountry = !country || mill.Country === country;
|
||
|
||
const production = parseFloat(mill['Annual Sugar Production (tons)']) || 0;
|
||
const matchesProduction = production >= minProduction;
|
||
|
||
return matchesSearch && matchesCountry && matchesProduction;
|
||
});
|
||
|
||
console.log('Filtered mills:', filteredMills.length);
|
||
|
||
// Update map display
|
||
updateMillsVisibility();
|
||
}
|
||
|
||
// Update visibility of mill markers based on filters
|
||
function updateMillsVisibility() {
|
||
mills.forEach((mill, index) => {
|
||
const key = `${parseFloat(mill.Latitude)},${parseFloat(mill.Longitude)}`;
|
||
const marker = millMarkers[key];
|
||
if (marker) {
|
||
const isVisible = filteredMills.includes(mill);
|
||
marker.setOpacity(isVisible ? 1 : 0.3);
|
||
marker.setRadius(isVisible ? getCircleSize(parseFloat(mill['Annual Sugar Production (tons)']) || 0) : getCircleSize(parseFloat(mill['Annual Sugar Production (tons)']) || 0));
|
||
}
|
||
});
|
||
}
|
||
|
||
// Reset all filters
|
||
function resetFilters() {
|
||
document.getElementById('searchInput').value = '';
|
||
document.getElementById('countryFilter').value = '';
|
||
document.getElementById('minProduction').value = '';
|
||
filteredMills = [...mills];
|
||
updateMillsVisibility();
|
||
}
|
||
|
||
// Mode switching
|
||
function attachModeListeners() {
|
||
const buttons = document.querySelectorAll('.mode-btn');
|
||
console.log(`Attaching listeners to ${buttons.length} mode buttons`);
|
||
|
||
buttons.forEach(btn => {
|
||
btn.addEventListener('click', (e) => {
|
||
console.log('Mode button clicked:', e.target.dataset.mode);
|
||
|
||
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
|
||
e.target.classList.add('active');
|
||
currentMode = e.target.dataset.mode;
|
||
|
||
console.log('Current mode:', currentMode);
|
||
|
||
document.getElementById('viewModePanel').style.display = currentMode === 'view' ? 'block' : 'none';
|
||
document.getElementById('drawModePanel').style.display = currentMode === 'draw' ? 'block' : 'none';
|
||
|
||
// Show/hide draw toolbar based on mode
|
||
const drawToolbar = document.querySelector('.leaflet-draw-toolbar');
|
||
if (drawToolbar) {
|
||
drawToolbar.style.display = currentMode === 'draw' ? 'block' : 'none';
|
||
}
|
||
|
||
updateDrawnItemsList();
|
||
});
|
||
});
|
||
}
|
||
|
||
// Update drawn items list
|
||
function updateDrawnItemsList() {
|
||
const list = document.getElementById('drawnItemsList');
|
||
|
||
if (drawnItems.size === 0) {
|
||
list.innerHTML = '<p style="color: #999; font-size: 12px;">No items drawn yet</p>';
|
||
document.getElementById('showFormBtn').classList.remove('visible');
|
||
document.getElementById('exportBtn').classList.remove('visible');
|
||
document.getElementById('clearDrawnBtn').classList.remove('visible');
|
||
} else {
|
||
const items = Array.from(drawnItems.entries()).map(([id, item]) => `
|
||
<div class="drawn-item">
|
||
<span>${item.data['Mill/Factory'] || `Point ${id.toString().slice(-4)}`}</span>
|
||
<button class="drawn-item-btn" onclick="removeDrawnItem(${id})">×</button>
|
||
</div>
|
||
`).join('');
|
||
|
||
list.innerHTML = items;
|
||
document.getElementById('showFormBtn').classList.add('visible');
|
||
document.getElementById('exportBtn').classList.add('visible');
|
||
document.getElementById('clearDrawnBtn').classList.add('visible');
|
||
}
|
||
}
|
||
|
||
// Remove drawn item
|
||
function removeDrawnItem(id) {
|
||
drawnItems.delete(id);
|
||
featureGroup.eachLayer(layer => {
|
||
if (layer.drawId === id) {
|
||
featureGroup.removeLayer(layer);
|
||
}
|
||
});
|
||
updateDrawnItemsList();
|
||
}
|
||
|
||
// Show edit form
|
||
function showEditForm(id) {
|
||
currentEditingId = id;
|
||
const item = drawnItems.get(id);
|
||
|
||
document.getElementById('editMill').value = item.data['Mill/Factory'] || '';
|
||
document.getElementById('editCountry').value = item.data['Country'] || '';
|
||
document.getElementById('editCompany').value = item.data['Company'] || '';
|
||
document.getElementById('editProvince').value = item.data['Province/Region'] || '';
|
||
document.getElementById('editLat').value = item.lat.toFixed(4);
|
||
document.getElementById('editLng').value = item.lng.toFixed(4);
|
||
document.getElementById('editCapacity').value = item.data['Crushing Capacity (tons/year)'] || '';
|
||
document.getElementById('editProduction').value = item.data['Annual Sugar Production (tons)'] || '';
|
||
document.getElementById('editNotes').value = item.data['Notes'] || '';
|
||
document.getElementById('editYear').value = item.data['Data Year'] || new Date().getFullYear();
|
||
document.getElementById('editAnnotations').value = item.data['Annotations'] || '';
|
||
|
||
document.getElementById('editModal').classList.add('active');
|
||
}
|
||
|
||
// Handle form submission
|
||
document.getElementById('editForm').addEventListener('submit', (e) => {
|
||
e.preventDefault();
|
||
|
||
const item = drawnItems.get(currentEditingId);
|
||
item.data = {
|
||
'Country': document.getElementById('editCountry').value,
|
||
'Mill/Factory': document.getElementById('editMill').value,
|
||
'Company': document.getElementById('editCompany').value,
|
||
'Province/Region': document.getElementById('editProvince').value,
|
||
'Latitude': parseFloat(document.getElementById('editLat').value),
|
||
'Longitude': parseFloat(document.getElementById('editLng').value),
|
||
'Crushing Capacity (tons/year)': document.getElementById('editCapacity').value,
|
||
'Annual Sugar Production (tons)': document.getElementById('editProduction').value,
|
||
'Notes': document.getElementById('editNotes').value,
|
||
'Data Year': document.getElementById('editYear').value,
|
||
'Annotations': document.getElementById('editAnnotations').value
|
||
};
|
||
|
||
drawnItems.set(currentEditingId, item);
|
||
updateDrawnItemsList();
|
||
closeModal('editModal');
|
||
});
|
||
|
||
// Modal controls
|
||
function closeModal(modalId) {
|
||
document.getElementById(modalId).classList.remove('active');
|
||
}
|
||
|
||
// Show feedback modal for existing mill
|
||
function showFeedbackModal(mill, country, lat, lng) {
|
||
document.getElementById('feedbackMill').value = mill;
|
||
document.getElementById('feedbackCountry').value = country;
|
||
document.getElementById('feedbackLocation').value = `${lat}, ${lng}`;
|
||
document.getElementById('feedbackType').value = '';
|
||
document.getElementById('feedbackMessage').value = '';
|
||
document.getElementById('feedbackName').value = '';
|
||
document.getElementById('feedbackEmail').value = '';
|
||
|
||
const feedbackModal = document.getElementById('feedbackModal');
|
||
feedbackModal.classList.add('active');
|
||
}
|
||
|
||
// Handle feedback form submission via Formspree
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const feedbackForm = document.getElementById('feedbackForm');
|
||
if (feedbackForm) {
|
||
feedbackForm.addEventListener('submit', function(e) {
|
||
e.preventDefault();
|
||
|
||
// Fetch will handle the form submission to Formspree
|
||
const formData = new FormData(this);
|
||
|
||
fetch(this.action, {
|
||
method: 'POST',
|
||
body: formData,
|
||
headers: {
|
||
'Accept': 'application/json'
|
||
}
|
||
})
|
||
.then(response => {
|
||
if (response.ok) {
|
||
// Show success message
|
||
alert('✅ Feedback sent successfully! Thank you for your input.');
|
||
closeModal('feedbackModal');
|
||
feedbackForm.reset();
|
||
} else {
|
||
alert('❌ Error sending feedback. Please try again.');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error:', error);
|
||
alert('❌ Error sending feedback. Please check your internet connection.');
|
||
});
|
||
});
|
||
}
|
||
});
|
||
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Escape') {
|
||
document.querySelectorAll('.modal').forEach(m => m.classList.remove('active'));
|
||
}
|
||
});
|
||
|
||
// Export to CSV - Option 1: Download locally
|
||
function exportToCSV() {
|
||
if (drawnItems.size === 0) {
|
||
alert('No items to export');
|
||
return;
|
||
}
|
||
|
||
// Header row
|
||
const headers = ['Country', 'Mill/Factory', 'Company', 'Province/Region', 'Latitude', 'Longitude', 'Crushing Capacity (tons/year)', 'Annual Sugar Production (tons)', 'Notes', 'Data Year', 'Annotations'];
|
||
const rows = [headers];
|
||
|
||
// Data rows
|
||
drawnItems.forEach(item => {
|
||
const row = [
|
||
item.data['Country'] || '',
|
||
item.data['Mill/Factory'] || '',
|
||
item.data['Company'] || '',
|
||
item.data['Province/Region'] || '',
|
||
item.lat,
|
||
item.lng,
|
||
item.data['Crushing Capacity (tons/year)'] || '',
|
||
item.data['Annual Sugar Production (tons)'] || '',
|
||
`"${(item.data['Notes'] || '').replace(/"/g, '""')}"`,
|
||
item.data['Data Year'] || '',
|
||
`"${(item.data['Annotations'] || '').replace(/"/g, '""')}"`
|
||
];
|
||
rows.push(row);
|
||
});
|
||
|
||
// Convert to CSV
|
||
const csv = rows.map(row =>
|
||
row.map(cell => {
|
||
if (typeof cell === 'string' && (cell.includes(',') || cell.includes('"') || cell.includes('\n'))) {
|
||
return `"${cell.replace(/"/g, '""')}"`;
|
||
}
|
||
return cell;
|
||
}).join(',')
|
||
).join('\n');
|
||
|
||
return csv;
|
||
}
|
||
|
||
// Export button - show options
|
||
document.getElementById('exportBtn').addEventListener('click', () => {
|
||
if (drawnItems.size === 0) {
|
||
alert('No items to export');
|
||
return;
|
||
}
|
||
|
||
showExportModal();
|
||
});
|
||
|
||
// Export modal
|
||
function showExportModal() {
|
||
const modal = document.createElement('div');
|
||
modal.className = 'modal active';
|
||
modal.id = 'exportModal';
|
||
modal.innerHTML = `
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h2>Send to Timon</h2>
|
||
<button class="close-btn" onclick="closeModal('exportModal')">×</button>
|
||
</div>
|
||
<div class="alert alert-info">
|
||
You have ${drawnItems.size} new mill location(s) to submit. Choose how to send them:
|
||
</div>
|
||
<div style="margin-bottom: 20px;">
|
||
<h3 style="font-size: 16px; color: #333; margin-bottom: 15px;">📤 Submit Online</h3>
|
||
<p style="color: #666; font-size: 14px; margin-bottom: 10px;">Send all new mills directly to the database. Your data will be reviewed and added.</p>
|
||
<button class="btn-primary" style="width: 100%;" onclick="submitViaFormspree()">Submit New Mills</button>
|
||
</div>
|
||
<div style="border-top: 1px solid #ddd; padding-top: 20px;">
|
||
<h3 style="font-size: 16px; color: #333; margin-bottom: 15px;">⬇️ Download as CSV</h3>
|
||
<p style="color: #666; font-size: 14px; margin-bottom: 10px;">Download the data as a CSV file for your records or manual submission.</p>
|
||
<button class="btn-secondary" style="width: 100%;" onclick="downloadCSV()">Download CSV File</button>
|
||
</div>
|
||
<div style="margin-top: 20px; text-align: center;">
|
||
<button class="btn-secondary" onclick="closeModal('exportModal')">Cancel</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
document.body.appendChild(modal);
|
||
}
|
||
|
||
// Submit via Formspree
|
||
function submitViaFormspree() {
|
||
const csv = exportToCSV();
|
||
|
||
// Build form data
|
||
const formData = new FormData();
|
||
formData.append('submissionType', 'New Mill Submissions');
|
||
formData.append('millCount', drawnItems.size);
|
||
formData.append('csvData', csv);
|
||
|
||
// Add individual mill details to the message
|
||
let millDetails = 'New Mills Submitted:\n\n';
|
||
let idx = 1;
|
||
drawnItems.forEach((item) => {
|
||
millDetails += `${idx}. ${item.data['Mill/Factory'] || 'Unnamed'}\n`;
|
||
millDetails += ` Country: ${item.data['Country'] || 'N/A'}\n`;
|
||
millDetails += ` Company: ${item.data['Company'] || 'N/A'}\n`;
|
||
millDetails += ` Province/Region: ${item.data['Province/Region'] || 'N/A'}\n`;
|
||
millDetails += ` Location: ${item.lat.toFixed(4)}, ${item.lng.toFixed(4)}\n`;
|
||
millDetails += ` Production: ${item.data['Annual Sugar Production (tons)'] || 'N/A'} tons/year\n`;
|
||
millDetails += ` Capacity: ${item.data['Crushing Capacity (tons/year)'] || 'N/A'} tons/year\n`;
|
||
millDetails += ` Notes: ${item.data['Notes'] || 'None'}\n`;
|
||
millDetails += ` Annotations: ${item.data['Annotations'] || 'None'}\n\n`;
|
||
idx++;
|
||
});
|
||
|
||
formData.append('message', millDetails);
|
||
|
||
// Submit to Formspree
|
||
fetch('https://formspree.io/f/xgvgybwl', {
|
||
method: 'POST',
|
||
body: formData,
|
||
headers: {
|
||
'Accept': 'application/json'
|
||
}
|
||
})
|
||
.then(response => {
|
||
if (response.ok) {
|
||
alert(`✅ Successfully submitted ${drawnItems.size} new mill(s)! Thank you for the contribution.`);
|
||
drawnItems.clear();
|
||
featureGroup.clearLayers();
|
||
updateDrawnItemsList();
|
||
closeModal('exportModal');
|
||
} else {
|
||
alert('❌ Error submitting mills. Please try again.');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error:', error);
|
||
alert('❌ Error submitting mills. Please check your internet connection.');
|
||
});
|
||
}
|
||
|
||
// Download CSV
|
||
function downloadCSV() {
|
||
const csv = exportToCSV();
|
||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||
const link = document.createElement('a');
|
||
const url = URL.createObjectURL(blob);
|
||
link.setAttribute('href', url);
|
||
link.setAttribute('download', `sugar_mills_additions_${new Date().toISOString().split('T')[0]}.csv`);
|
||
link.style.visibility = 'hidden';
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
closeModal('exportModal');
|
||
}
|
||
|
||
// Email CSV via mailto
|
||
function emailCSV() {
|
||
const csv = exportToCSV();
|
||
const filename = `sugar_mills_additions_${new Date().toISOString().split('T')[0]}.csv`;
|
||
|
||
// Create summary for email body
|
||
let summary = 'New Sugar Cane Mill Locations\n\n';
|
||
summary += `Attached: ${filename}\n\n`;
|
||
summary += 'Summary of new mills:\n\n';
|
||
|
||
drawnItems.forEach((item, idx) => {
|
||
summary += `${idx + 1}. ${item.data['Mill/Factory'] || 'Unnamed'} (${item.data['Country'] || 'Unknown'})\n`;
|
||
summary += ` Company: ${item.data['Company'] || 'N/A'}\n`;
|
||
summary += ` Location: ${item.lat.toFixed(4)}, ${item.lng.toFixed(4)}\n`;
|
||
summary += ` Production: ${item.data['Annual Sugar Production (tons)'] || 'N/A'} tons/year\n\n`;
|
||
});
|
||
|
||
// First, download the CSV file
|
||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||
const url = URL.createObjectURL(blob);
|
||
const link = document.createElement('a');
|
||
link.setAttribute('href', url);
|
||
link.setAttribute('download', filename);
|
||
link.style.visibility = 'hidden';
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
|
||
// Then open email client with instructions
|
||
const subject = encodeURIComponent(`New Sugar Cane Mills Data - ${new Date().toISOString().split('T')[0]}`);
|
||
const body = encodeURIComponent(summary);
|
||
const mailto = `mailto:timon@resiliencebv.com?subject=${subject}&body=${body}`;
|
||
|
||
// Show info that file was downloaded
|
||
setTimeout(() => {
|
||
alert(`✅ CSV file downloaded: ${filename}\n\nPlease attach it to the email that will now open, then send it.`);
|
||
window.open(mailto);
|
||
}, 500);
|
||
|
||
closeModal('exportModal');
|
||
}
|
||
|
||
// Show form button
|
||
document.getElementById('showFormBtn').addEventListener('click', () => {
|
||
if (drawnItems.size > 0) {
|
||
const firstId = drawnItems.keys().next().value;
|
||
showEditForm(firstId);
|
||
}
|
||
});
|
||
|
||
// Clear all
|
||
document.getElementById('clearDrawnBtn').addEventListener('click', () => {
|
||
if (confirm('Clear all drawn items?')) {
|
||
drawnItems.clear();
|
||
featureGroup.clearLayers();
|
||
updateDrawnItemsList();
|
||
}
|
||
});
|
||
|
||
// Initialize on load
|
||
window.addEventListener('load', function() {
|
||
initMap();
|
||
});
|
||
|
||
// Also try immediate initialization if document is already loaded
|
||
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||
setTimeout(function() {
|
||
initMap();
|
||
}, 500);
|
||
}
|