diff --git a/html/static/js/photo-upload.js b/html/static/js/photo-upload.js
new file mode 100644
index 00000000..3759e286
--- /dev/null
+++ b/html/static/js/photo-upload.js
@@ -0,0 +1,164 @@
+class PhotoUpload extends HTMLElement {
+ constructor() {
+ super();
+ this.attachShadow({ mode: 'open' });
+ this.render();
+ }
+
+ connectedCallback() {
+ setTimeout(() => this._initializeUploader(), 0);
+ }
+
+ _initializeUploader() {
+ // Elements
+ const photoInput = this.shadowRoot.querySelector('#photos');
+
+ // Handle photo selection
+ photoInput.addEventListener('change', this._handlePhotoSelection);
+
+ // Handle drag and drop
+ const photoDropArea = this.shadowRoot.querySelector('#photoDropArea');
+
+ photoDropArea.addEventListener('dragover', function(e) {
+ e.preventDefault();
+ photoDropArea.style.backgroundColor = '#e9ecef';
+ });
+
+ photoDropArea.addEventListener('dragleave', function() {
+ photoDropArea.style.backgroundColor = '#f8f9fa';
+ });
+
+ photoDropArea.addEventListener('drop', function(e) {
+ e.preventDefault();
+ photoDropArea.style.backgroundColor = '#f8f9fa';
+
+ if (e.dataTransfer.files.length) {
+ handleFiles(e.dataTransfer.files);
+ }
+ });
+ }
+ render() {
+ const style = `
+
+
+ `;
+
+ // Create the table
+ let html = `
+
+
+
+
+
+
+
Take pictures of the mosquito problem area
+
+
+
+
+
+
+ `;
+
+ // Set the shadow DOM content
+ this.shadowRoot.innerHTML = style + html;
+ }
+ /**
+ * Handle photo selection and preview
+ */
+ _handlePhotoSelection() {
+ const photoInput = document.getElementById('photos');
+ const photoPreviewContainer = document.getElementById('photoPreviewContainer');
+
+ // 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) => {
+ console.log("Handling", index, file);
+ if (!file.type.match('image.*')) {
+ console.log("Skipping non-image file", file.type);
+ 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 = '×';
+ 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);
+ });
+ }
+ }
+
+}
+
+// Register the custom element
+customElements.define('photo-upload', PhotoUpload);
diff --git a/rmo/template/nuisance.html b/rmo/template/nuisance.html
index a1f82ada..44c50e66 100644
--- a/rmo/template/nuisance.html
+++ b/rmo/template/nuisance.html
@@ -8,6 +8,7 @@
+