Mill viewer updates and fixes

This commit is contained in:
Nik Verweel 2026-01-27 10:32:39 +01:00
parent 40a7d4d691
commit f231911d7b
2 changed files with 43 additions and 116 deletions

View file

@ -2,7 +2,7 @@
const countryColors = {
'South Africa': '#FF6B6B',
'Zimbabwe': '#4ECDC4',
'Eswatini': '#45B7D1',
'eSwatini': '#45B7D1',
'Mozambique': '#96CEB4',
'Malawi': '#FFEAA7',
'Zambia': '#DDA15E',
@ -10,7 +10,8 @@ const countryColors = {
'Kenya': '#F1A208',
'Tanzania': '#A23B72',
'Rwanda': '#8E7DBE',
'Burundi': '#C9ADA7'
'Burundi': '#C9ADA7',
'Mexico': '#006846',
};
let map;
@ -318,12 +319,15 @@ function parseCSV(csvText) {
const lines = csvText.trim().split('\n');
const headers = lines[0].split(',').map(h => h.trim());
mills = lines.slice(1).map(line => {
mills = lines.slice(1).map((line, index) => {
const values = parseCSVLine(line);
const row = {};
headers.forEach((header, idx) => {
row[header] = values[idx] ? values[idx].trim() : '';
});
row._id = index; // Internal unique ID
return row;
}).filter(row => row.Latitude && row.Longitude);
}
@ -358,12 +362,17 @@ function parseCSVLine(line) {
// Render mills on map
function renderMills() {
Object.values(millMarkers).forEach(marker => {
if (map.hasLayer(marker)) map.removeLayer(marker);
});
millMarkers = {};
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 production = cleanNumber(mill['Annual Sugar Production (tons)']);
const size = getCircleSize(production);
const color = countryColors[mill.Country] || '#999';
@ -379,7 +388,7 @@ function renderMills() {
const popup = createPopup(mill);
marker.bindPopup(popup);
millMarkers[`${lat},${lng}`] = marker;
millMarkers[mill._id] = marker;
}
});
@ -429,7 +438,7 @@ function createPopup(mill) {
${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>
<a href="https://docs.google.com/spreadsheets/d/1ZHEIyhupNDHVd1EScBn0DnuiAzMFoZcAPZm3U65abkY/edit?gid=220881066#gid=220881066" target="_blank" style="display: block; width: 100%; padding: 8px; background: #2596be; color: white; text-align: center; text-decoration: none; border-radius: 4px; font-size: 12px; font-weight: 600;">📝 Edit on Google Sheets</a>
</div>
</div>
`;
@ -467,6 +476,13 @@ function updateLegend() {
countryFilter.value = currentValue;
}
// Helper to clean numbers (remove commas and spaces)
function cleanNumber(val) {
if (!val) return 0;
const cleanStr = String(val).replace(/[, ]/g, '');
return parseFloat(cleanStr) || 0;
}
// Apply filters to mills
function applyFilters() {
const search = document.getElementById('searchInput').value.toLowerCase();
@ -485,7 +501,7 @@ function applyFilters() {
const matchesCountry = !country || mill.Country === country;
const production = parseFloat(mill['Annual Sugar Production (tons)']) || 0;
const production = cleanNumber(mill['Annual Sugar Production (tons)']);
const matchesProduction = production >= minProduction;
return matchesSearch && matchesCountry && matchesProduction;
@ -499,13 +515,25 @@ function applyFilters() {
// 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];
mills.forEach(mill => {
const marker = millMarkers[mill._id];
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));
if (isVisible) {
if (!map.hasLayer(marker)) {
marker.addTo(map);
}
marker.setStyle({
opacity: 0.8,
fillOpacity: 0.7
});
} else {
if (map.hasLayer(marker)) {
map.removeLayer(marker);
}
}
}
});
}
@ -632,55 +660,6 @@ 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'));

View file

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sugar Cane Mills - East & Southern Africa</title>
<title>Sugar Cane Mills - East & South Africa & Mexico</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet-measure@3.1.0/dist/leaflet-measure.css" />
@ -418,7 +418,7 @@
</script>
<header>
<button class="back-btn" onclick="window.location.href='../';" title="Back to main tools">← Back</button>
<h1>🗺️ Sugar Cane Mills - East & Southern Africa</h1>
<h1>🗺️ Sugar Cane Mills - East & South Africa & Mexico</h1>
<div class="header-controls">
<div class="mode-toggle">
<button class="mode-btn active" data-mode="view">View</button>
@ -445,7 +445,7 @@
<select id="countryFilter" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; margin-bottom: 8px;">
<option value="">All Countries</option>
</select>
<input type="number" id="minProduction" placeholder="Min production (tons)" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; margin-bottom: 8px;">
<input type="number" id="minProduction" placeholder="Min production (tons/year)" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; margin-bottom: 8px;">
<button class="btn-secondary" id="resetFiltersBtn" style="width: 100%; padding: 8px;">Reset Filters</button>
</div>
@ -564,58 +564,6 @@
</div>
</div>
<!-- Feedback Modal -->
<div id="feedbackModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Send Feedback on Mill</h2>
<button class="close-btn" onclick="closeModal('feedbackModal')">&times;</button>
</div>
<form id="feedbackForm" action="https://formspree.io/f/xgvgybwl" method="POST">
<div class="form-group">
<label>Mill Name</label>
<input type="text" name="mill" id="feedbackMill" readonly style="background: #f5f5f5;">
</div>
<div class="form-group">
<label>Country</label>
<input type="text" name="country" id="feedbackCountry" readonly style="background: #f5f5f5;">
</div>
<div class="form-group">
<label>Location</label>
<input type="text" name="location" id="feedbackLocation" readonly style="background: #f5f5f5;">
</div>
<div class="form-group">
<label>Feedback Type *</label>
<select name="feedbackType" id="feedbackType" required style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px;">
<option value="">-- Select type --</option>
<option value="Correction">Data Correction</option>
<option value="Addition">Add Missing Data</option>
<option value="Update">Update Information</option>
<option value="Verification">Verification/Confirmation</option>
<option value="Other">Other Feedback</option>
</select>
</div>
<div class="form-group">
<label>Your Message *</label>
<textarea name="message" id="feedbackMessage" required style="min-height: 120px;"></textarea>
</div>
<div class="form-group">
<label>Your Name (Optional)</label>
<input type="text" name="name" id="feedbackName" placeholder="Your name or organization">
</div>
<div class="form-group">
<label>Your Email (Optional)</label>
<input type="email" name="email" id="feedbackEmail" placeholder="your@email.com">
</div>
<input type="hidden" name="_next" value="javascript:void(0)" />
<div class="form-actions">
<button type="button" class="btn-secondary" onclick="closeModal('feedbackModal')">Cancel</button>
<button type="submit" class="btn-primary">Send Feedback</button>
</div>
</form>
</div>
</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.js"></script>
<script src="https://unpkg.com/leaflet-measure@3.1.0/dist/leaflet-measure.umd.js"></script>