Spaces:
Running
Running
/** | |
* 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 = ` | |
<input type="checkbox" value="${option}"> ${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 = ` | |
<div class="photo-category-header"> | |
<div class="photo-category-title"> | |
<div class="photo-category-icon">IMG</div> | |
${category} | |
</div> | |
</div> | |
<div class="photo-upload-area"> | |
<div class="photo-upload" data-category="${category}"> | |
<div class="photo-upload-icon">+</div> | |
<div>Click to select ${category} photo</div> | |
</div> | |
<button type="button" class="camera-btn" data-category="${category}"> | |
Camera | |
</button> | |
</div> | |
<div class="uploaded-file" id="photo-${category}" style="display: none;"></div> | |
`; | |
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 = `<div class="uploaded-file">${filename} uploaded successfully</div>`; | |
} | |
} | |
} | |
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; | |
} | |
} | |