/** * Form Manager Module * Handles form operations, validation, and data management */ export class FormManager { constructor(apiClient, uiManager) { this.apiClient = apiClient; this.uiManager = uiManager; this.uploadedPhotos = {}; this.audioFile = null; this.currentEditId = null; } async initialize() { try { const formOptions = await this.apiClient.loadFormOptions(); this.renderMultiSelect('utilityOptions', formOptions.utilities); this.renderMultiSelect('phenologyOptions', formOptions.phenologyStages); this.renderPhotoCategories(formOptions.photoCategories); } catch (error) { console.error('Error initializing form:', error); throw error; } } renderMultiSelect(containerId, options) { const container = document.getElementById(containerId); if (!container) return; container.innerHTML = ''; options.forEach(option => { const label = document.createElement('label'); label.innerHTML = ` ${option} `; container.appendChild(label); }); } renderPhotoCategories(categories) { const container = document.getElementById('photoCategories'); if (!container) return; container.innerHTML = ''; categories.forEach(category => { const categoryDiv = document.createElement('div'); categoryDiv.className = 'photo-category'; categoryDiv.innerHTML = `
IMG
${category}
+
Click to select ${category} photo
`; container.appendChild(categoryDiv); }); } getSelectedValues(containerId) { const container = document.getElementById(containerId); if (!container) return []; const checkboxes = container.querySelectorAll('input[type="checkbox"]:checked'); return Array.from(checkboxes).map(cb => cb.value); } getFormData() { const utilityValues = this.getSelectedValues('utilityOptions'); const phenologyValues = this.getSelectedValues('phenologyOptions'); return { latitude: this.getNumericValue('latitude'), longitude: this.getNumericValue('longitude'), location_name: this.getStringValue('locationName'), local_name: this.getStringValue('localName'), scientific_name: this.getStringValue('scientificName'), common_name: this.getStringValue('commonName'), tree_code: this.getStringValue('treeCode'), height: this.getNumericValue('height'), width: this.getNumericValue('width'), utility: utilityValues.length > 0 ? utilityValues : [], phenology_stages: phenologyValues.length > 0 ? phenologyValues : [], storytelling_text: this.getStringValue('storytellingText'), storytelling_audio: this.audioFile, photographs: Object.keys(this.uploadedPhotos).length > 0 ? this.uploadedPhotos : null, notes: this.getStringValue('notes') }; } getNumericValue(fieldId) { const element = document.getElementById(fieldId); if (!element || !element.value) return null; const value = parseFloat(element.value); return isNaN(value) ? null : value; } getStringValue(fieldId) { const element = document.getElementById(fieldId); return element && element.value ? element.value : null; } populateForm(treeData) { this.setFieldValue('latitude', treeData.latitude); this.setFieldValue('longitude', treeData.longitude); this.setFieldValue('locationName', treeData.location_name || ''); this.setFieldValue('localName', treeData.local_name || ''); this.setFieldValue('scientificName', treeData.scientific_name || ''); this.setFieldValue('commonName', treeData.common_name || ''); this.setFieldValue('treeCode', treeData.tree_code || ''); this.setFieldValue('height', treeData.height || ''); this.setFieldValue('width', treeData.width || ''); this.setFieldValue('storytellingText', treeData.storytelling_text || ''); this.setFieldValue('notes', treeData.notes || ''); // Handle utility checkboxes if (treeData.utility && Array.isArray(treeData.utility)) { this.setCheckboxValues('utilityOptions', treeData.utility); } // Handle phenology checkboxes if (treeData.phenology_stages && Array.isArray(treeData.phenology_stages)) { this.setCheckboxValues('phenologyOptions', treeData.phenology_stages); } } setFieldValue(fieldId, value) { const element = document.getElementById(fieldId); if (element) { element.value = value; } } setCheckboxValues(containerId, values) { const container = document.getElementById(containerId); if (!container) return; container.querySelectorAll('input[type="checkbox"]').forEach(checkbox => { checkbox.checked = values.includes(checkbox.value); }); } resetForm(silent = false) { const form = document.getElementById('treeForm'); if (form) { form.reset(); } this.uploadedPhotos = {}; this.audioFile = null; this.currentEditId = null; // Clear uploaded file indicators document.querySelectorAll('.uploaded-file').forEach(el => { el.style.display = 'none'; el.innerHTML = ''; }); // Reset audio controls this.resetAudioControls(); if (!silent) { this.uiManager.showMessage('Form has been reset.', 'success'); } } resetAudioControls() { const audioElement = document.getElementById('audioPlayback'); if (audioElement) { audioElement.classList.add('hidden'); audioElement.src = ''; } const recordingStatus = document.getElementById('recordingStatus'); if (recordingStatus) { recordingStatus.textContent = 'Click to start recording'; } const audioUploadResult = document.getElementById('audioUploadResult'); if (audioUploadResult) { audioUploadResult.innerHTML = ''; } } async handleFileUpload(file, type, category = null) { try { const result = await this.apiClient.uploadFile(file, type, category); if (type === 'image' && category) { this.uploadedPhotos[category] = result.filename; this.showUploadSuccess(category, file.name, 'photo'); } else if (type === 'audio') { this.audioFile = result.filename; this.showUploadSuccess(null, file.name, 'audio'); } return result; } catch (error) { console.error(`Error uploading ${type}:`, error); this.uiManager.showMessage(`Error uploading ${type}: ${error.message}`, 'error'); throw error; } } showUploadSuccess(category, filename, type) { if (type === 'photo' && category) { const resultDiv = document.getElementById(`photo-${category}`); if (resultDiv) { resultDiv.style.display = 'block'; resultDiv.innerHTML = `${filename} uploaded successfully`; } } else if (type === 'audio') { const resultDiv = document.getElementById('audioUploadResult'); if (resultDiv) { resultDiv.innerHTML = `
${filename} uploaded successfully
`; } } } setEditMode(treeId) { this.currentEditId = treeId; // Update submit button const submitBtn = document.querySelector('button[type="submit"]'); if (submitBtn) { submitBtn.textContent = 'Update Tree Record'; } // Add cancel edit button if it doesn't exist this.addCancelButton(); } addCancelButton() { if (document.getElementById('cancelEdit')) return; const cancelBtn = document.createElement('button'); cancelBtn.type = 'button'; cancelBtn.id = 'cancelEdit'; cancelBtn.className = 'tt-btn tt-btn-outline'; cancelBtn.textContent = 'Cancel Edit'; const formActions = document.querySelector('.form-actions'); const submitBtn = document.querySelector('button[type="submit"]'); if (formActions && submitBtn) { formActions.insertBefore(cancelBtn, submitBtn); } } exitEditMode() { this.currentEditId = null; // Restore original submit button text const submitBtn = document.querySelector('button[type="submit"]'); if (submitBtn) { submitBtn.textContent = 'Save Tree Record'; } // Remove cancel button const cancelBtn = document.getElementById('cancelEdit'); if (cancelBtn) { cancelBtn.remove(); } } isInEditMode() { return this.currentEditId !== null; } getCurrentEditId() { return this.currentEditId; } validateForm() { const latitude = this.getNumericValue('latitude'); const longitude = this.getNumericValue('longitude'); if (latitude === null || longitude === null) { throw new Error('Latitude and longitude are required'); } if (latitude < -90 || latitude > 90) { throw new Error('Latitude must be between -90 and 90 degrees'); } if (longitude < -180 || longitude > 180) { throw new Error('Longitude must be between -180 and 180 degrees'); } return true; } }