nidus-sync/htmlpage/public-reports/template/quick.html
Eli Ribble b35c9496b6
Add the ability to register for updates on quick reports
At this point it also appears that I'm correctly capturing the GPS
location as both PostGIS data and as an H3 cell.
2026-01-08 15:34:48 +00:00

300 lines
9.8 KiB
HTML

{{template "base.html" .}}
{{define "title"}}Dash{{end}}
{{define "extraheader"}}
<style>
.district-logo {
max-height: 60px;
width: auto;
}
.photo-upload-area {
border: 2px dashed #ccc;
border-radius: 8px;
padding: 20px;
text-align: center;
margin-bottom: 20px;
background-color: #f9f9f9;
}
.photo-preview {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 15px;
}
.photo-preview img {
width: 80px;
height: 80px;
object-fit: cover;
border-radius: 4px;
}
.submit-btn {
padding: 15px 0;
font-size: 1.25rem;
border-radius: 8px;
}
.location-info {
background-color: #e9f5ff;
border-radius: 8px;
padding: 12px;
margin-bottom: 20px;
font-size: 0.9rem;
}
@media (max-width: 767px) {
.header-title {
font-size: 1.5rem;
}
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Elements
const form = document.getElementById('mosquitoReportForm');
const photoInput = document.getElementById('photos');
const photoPreviewContainer = document.getElementById('photoPreviewContainer');
const locationStatus = document.getElementById('locationStatus');
const latitudeInput = document.getElementById('latitude');
const longitudeInput = document.getElementById('longitude');
const submitButton = document.getElementById('submitButton');
const loadingOverlay = document.getElementById('loadingOverlay');
// Get current location
requestLocation();
// Handle photo selection
photoInput.addEventListener('change', handlePhotoSelection);
// Handle form submission
form.addEventListener('submit', handleFormSubmission);
/**
* Request user's geolocation
*/
function requestLocation() {
if (navigator.geolocation) {
locationStatus.textContent = "Requesting your location...";
navigator.geolocation.getCurrentPosition(
// Success callback
function(position) {
locationStatus.textContent = "Location successfully added";
locationStatus.classList.add('text-success');
// Store location in hidden fields
latitudeInput.value = position.coords.latitude;
longitudeInput.value = position.coords.longitude;
},
// Error callback
function(error) {
let errorMessage = "Unable to get your location";
switch(error.code) {
case error.PERMISSION_DENIED:
errorMessage = "Location access denied. Please enable location services.";
break;
case error.POSITION_UNAVAILABLE:
errorMessage = "Location information unavailable.";
break;
case error.TIMEOUT:
errorMessage = "Location request timed out.";
break;
}
locationStatus.textContent = errorMessage;
locationStatus.classList.add('text-danger');
},
// Options
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
}
);
} else {
locationStatus.textContent = "Geolocation is not supported by your browser";
locationStatus.classList.add('text-danger');
}
}
/**
* Handle photo selection and preview
*/
function handlePhotoSelection() {
// Clear previous previews
photoPreviewContainer.innerHTML = '';
// Check if files were selected
if (photoInput.files && photoInput.files.length > 0) {
// Loop through selected files
Array.from(photoInput.files).forEach((file, index) => {
if (!file.type.match('image.*')) {
return; // Skip non-image files
}
// Create preview container
const previewContainer = document.createElement('div');
previewContainer.className = 'position-relative m-1';
// Create image preview
const img = document.createElement('img');
img.className = 'img-thumbnail';
img.style.width = '100px';
img.style.height = '100px';
img.style.objectFit = 'cover';
// Read file and set preview
const reader = new FileReader();
reader.onload = (e) => {
img.src = e.target.result;
};
reader.readAsDataURL(file);
// Create remove button
const removeBtn = document.createElement('button');
removeBtn.type = 'button';
removeBtn.className = 'btn btn-sm btn-danger position-absolute top-0 end-0';
removeBtn.innerHTML = '&times;';
removeBtn.style.fontSize = '10px';
removeBtn.style.padding = '0 5px';
// Handle remove button click
removeBtn.addEventListener('click', function() {
// Create a new FileList without this file
// Since FileList is immutable, we need to reset the input
// This is a bit tricky and requires recreating the input
previewContainer.remove();
// If this was the last image, clear the input entirely
if (photoPreviewContainer.children.length === 0) {
photoInput.value = '';
}
// Note: Unfortunately, selectively removing files from a FileList isn't straightforward
// In a real implementation, we might track selected files in an array and recreate the input
});
// Add elements to the preview container
previewContainer.appendChild(img);
previewContainer.appendChild(removeBtn);
photoPreviewContainer.appendChild(previewContainer);
});
}
}
/**
* Handle form submission
*/
function handleFormSubmission(event) {
event.preventDefault();
// Show loading overlay
loadingOverlay.classList.remove('d-none');
// Disable submit button to prevent double submission
submitButton.disabled = true;
// Create FormData object
const formData = new FormData(form);
// Send AJAX request
fetch(form.action, {
method: 'POST',
body: formData,
})
.then(response => {
if (response.ok) {
// Navigate to the URL the server specified
window.location.href = response.url;
return;
}
console.error("not ok server response", response);
throw new Error("Server error " + response.status);
})
.catch(error => {
console.error('Error:', error);
alert('There was a problem submitting your report. Please try again. If this happens a few times, please let us know.');
// Re-enable submit button
submitButton.disabled = false;
// Hide loading overlay
loadingOverlay.classList.add('d-none');
});
}
});
</script>
{{end}}
{{define "content"}}
<!-- Main Content -->
<main class="container mb-5">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="card shadow-sm">
<div class="card-body p-4">
<h2 class="card-title text-center mb-4">Quick Mosquito Report</h2>
<!-- Form -->
<form id="mosquitoReportForm" action="/quick-submit" method="POST" enctype="multipart/form-data">
<!-- Location Automatic Collection Note -->
<div class="location-info d-flex align-items-center mb-4">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-geo-alt me-2" viewBox="0 0 16 16">
<path d="M12.166 8.94c-.524 1.062-1.234 2.12-1.96 3.07A31.493 31.493 0 0 1 8 14.58a31.481 31.481 0 0 1-2.206-2.57c-.726-.95-1.436-2.008-1.96-3.07C3.304 7.867 3 6.862 3 6a5 5 0 0 1 10 0c0 .862-.305 1.867-.834 2.94zM8 16s6-5.686 6-10A6 6 0 0 0 2 6c0 4.314 6 10 6 10z"/>
<path d="M8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 1a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
</svg>
<span id="locationStatus">Requesting your location...</span>
<!-- Hidden fields for location data -->
<input type="hidden" id="latitude" name="latitude">
<input type="hidden" id="longitude" name="longitude">
</div>
<!-- Photo Upload -->
<div class="mb-4">
<label for="photos" class="form-label fw-bold">Photos (Optional)</label>
<div class="photo-upload-area">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" class="bi bi-camera mb-2" viewBox="0 0 16 16">
<path d="M15 12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h1.172a3 3 0 0 0 2.12-.879l.83-.828A1 1 0 0 1 6.827 3h2.344a1 1 0 0 1 .707.293l.828.828A3 3 0 0 0 12.828 5H14a1 1 0 0 1 1 1v6zM2 4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-1.172a2 2 0 0 1-1.414-.586l-.828-.828A2 2 0 0 0 9.172 2H6.828a2 2 0 0 0-1.414.586l-.828.828A2 2 0 0 1 3.172 4H2z"/>
<path d="M8 11a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5zm0 1a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7zM3 6.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0z"/>
</svg>
<div>
<input type="file" id="photos" name="photos" class="d-none" accept="image/*" multiple>
<button type="button" class="btn btn-outline-primary mb-2" onclick="document.getElementById('photos').click()">Add Photos</button>
</div>
<small class="d-block text-muted">Take pictures of the mosquito problem area</small>
<!-- Photo Preview Area -->
<div id="photoPreviewContainer" class="photo-preview mt-3 d-flex flex-wrap">
<!-- Image previews will be added here by JavaScript -->
</div>
</div>
</div>
<!-- Comments -->
<div class="mb-4">
<label for="comments" class="form-label fw-bold">Comments</label>
<textarea class="form-control" id="comments" name="comments" rows="4" placeholder="Describe the mosquito issue (e.g., standing water, high mosquito activity, time of day they're most active)"></textarea>
</div>
<!-- Submit Button -->
<button type="submit" class="btn btn-success w-100 submit-btn mt-4" id="submitButton">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-send-fill me-2" viewBox="0 0 16 16">
<path d="M15.964.686a.5.5 0 0 0-.65-.65L.767 5.855H.766l-.452.18a.5.5 0 0 0-.082.887l.41.26.001.002 4.995 3.178 3.178 4.995.002.002.26.41a.5.5 0 0 0 .886-.083l6-15Zm-1.833 1.89L6.637 10.07l-.215-.338a.5.5 0 0 0-.154-.154l-.338-.215 7.494-7.494 1.178-.471-.47 1.178Z"/>
</svg>
Submit Report
</button>
</form>
</div>
</div>
</div>
</div>
</main>
<!-- Loading Indicator Overlay (Initially hidden) -->
<div id="loadingOverlay" class="position-fixed top-0 start-0 w-100 h-100 d-none" style="background-color: rgba(0,0,0,0.5); z-index: 1050;">
<div class="position-absolute top-50 start-50 translate-middle text-white text-center">
<div class="spinner-border" role="status"></div>
<p class="mt-2">Submitting your report...</p>
</div>
</div>
{{end}}