Spaces:
Running
Running
/** | |
* UI Manager Module | |
* Handles user interface updates, messages, and tree list display | |
*/ | |
export class UIManager { | |
constructor(authManager) { | |
this.authManager = authManager; | |
} | |
initialize() { | |
this.displayUserInfo(); | |
this.loadSelectedLocation(); | |
} | |
displayUserInfo() { | |
if (!this.authManager.currentUser) return; | |
const userNameEl = document.getElementById('userName'); | |
const userRoleEl = document.getElementById('userRole'); | |
const userAvatarEl = document.getElementById('userAvatar'); | |
if (userNameEl) { | |
userNameEl.textContent = this.authManager.currentUser.full_name; | |
} | |
if (userRoleEl) { | |
userRoleEl.textContent = this.authManager.currentUser.role; | |
} | |
if (userAvatarEl) { | |
userAvatarEl.textContent = this.authManager.currentUser.full_name.charAt(0).toUpperCase(); | |
} | |
} | |
loadSelectedLocation() { | |
const selectedLocation = localStorage.getItem('selectedLocation'); | |
if (selectedLocation) { | |
try { | |
const location = JSON.parse(selectedLocation); | |
const latElement = document.getElementById('latitude'); | |
const lngElement = document.getElementById('longitude'); | |
if (latElement && lngElement) { | |
latElement.value = location.lat.toFixed(6); | |
lngElement.value = location.lng.toFixed(6); | |
// Clear the stored location | |
localStorage.removeItem('selectedLocation'); | |
this.showMessage('Location loaded from map!', 'success'); | |
} | |
} catch (error) { | |
console.error('Error loading selected location:', error); | |
} | |
} | |
} | |
showMessage(message, type) { | |
const messageDiv = document.getElementById('message'); | |
if (!messageDiv) return; | |
messageDiv.className = `message ${type === 'error' ? 'error' : 'success'}`; | |
messageDiv.textContent = message; | |
// Auto-hide after 5 seconds | |
setTimeout(() => { | |
messageDiv.textContent = ''; | |
messageDiv.className = ''; | |
}, 5000); | |
} | |
renderTreeList(trees) { | |
const treeList = document.getElementById('treeList'); | |
if (!treeList) return; | |
if (trees.length === 0) { | |
treeList.innerHTML = '<div class="loading">No trees recorded yet</div>'; | |
return; | |
} | |
// Compute display numbers for Ishita's trees created today | |
const now = new Date(); | |
const y = now.getFullYear(), m = now.getMonth(), d = now.getDate(); | |
const isToday = (ts) => { try { const t=new Date(ts); return t.getFullYear()===y && t.getMonth()===m && t.getDate()===d; } catch(_) { return false; } }; | |
const ishitaToday = trees.filter(t => (t.created_by||'').toLowerCase()==='ishita' && isToday(t.created_at)); | |
// Sort by created_at ascending to assign small to early ones | |
ishitaToday.sort((a,b) => new Date(a.created_at) - new Date(b.created_at)); | |
const ishitaIndex = new Map(); | |
ishitaToday.forEach((t, idx) => ishitaIndex.set(t.id, idx+1)); | |
treeList.innerHTML = trees.map(tree => { | |
const canEdit = this.authManager.canEditTree(tree.created_by); | |
const canDelete = this.authManager.canDeleteTree(tree.created_by); | |
return ` | |
<div class="tree-item" data-tree-id="${tree.id}"> | |
<div class="tree-header"> | |
<div class="tree-id">Tree #${tree.id}${ishitaIndex.has(tree.id) ? ` (Ishita No. ${ishitaIndex.get(tree.id)})` : ''}</div> | |
<div class="tree-actions"> | |
${canEdit ? `<button class="btn-icon edit-tree" data-tree-id="${tree.id}" title="Edit Tree">Edit</button>` : ''} | |
${canDelete ? `<button class="btn-icon delete-tree" data-tree-id="${tree.id}" title="Delete Tree">Delete</button>` : ''} | |
</div> | |
</div> | |
<div class="tree-info"> | |
${tree.scientific_name || tree.common_name || tree.local_name || 'Unnamed'} | |
${tree.location_name ? `<br><strong>Location:</strong> ${this.escapeHtml(tree.location_name)}` : ''} | |
<br>${tree.latitude.toFixed(4)}, ${tree.longitude.toFixed(4)} | |
${tree.tree_code ? `<br>Code: ${tree.tree_code}` : ''} | |
<br>${new Date(tree.created_at).toLocaleDateString()} | |
<br>By: ${tree.created_by || 'Unknown'} | |
</div> | |
</div> | |
`; | |
}).join(''); | |
} | |
escapeHtml(text) { | |
if (typeof text !== 'string') return text; | |
return text | |
.replace(/&/g, '&') | |
.replace(/</g, '<') | |
.replace(/>/g, '>') | |
.replace(/"/g, '"') | |
.replace(/'/g, '''); | |
} | |
showLoadingState(containerId, message = 'Loading...') { | |
const container = document.getElementById(containerId); | |
if (!container) return; | |
container.innerHTML = ` | |
<div class="loading"> | |
<div class="spinner"></div> | |
${message} | |
</div> | |
`; | |
} | |
showErrorState(containerId, message = 'Error loading content') { | |
const container = document.getElementById(containerId); | |
if (!container) return; | |
container.innerHTML = `<div class="loading">${message}</div>`; | |
} | |
updateLocationButtonState(isGetting) { | |
const locationBtn = document.getElementById('getLocation'); | |
if (!locationBtn) return; | |
locationBtn.textContent = isGetting ? 'Getting...' : 'Get GPS Location'; | |
locationBtn.disabled = isGetting; | |
} | |
highlightAutoFilledField(fieldId) { | |
const input = document.getElementById(fieldId); | |
if (input && !input.value.trim()) { | |
input.style.backgroundColor = '#f0f9ff'; | |
setTimeout(() => { | |
input.style.backgroundColor = ''; | |
}, 2000); | |
} | |
} | |
createFileInput(accept, capture = false) { | |
const input = document.createElement('input'); | |
input.type = 'file'; | |
input.accept = accept; | |
if (capture) { | |
input.capture = 'environment'; | |
} | |
return input; | |
} | |
confirmDeletion(treeId) { | |
return confirm(`Are you sure you want to delete Tree #${treeId}? This action cannot be undone.`); | |
} | |
scrollToTop() { | |
window.scrollTo({ top: 0, behavior: 'smooth' }); | |
} | |
focusFirstError() { | |
const firstError = document.querySelector('.form-input.error, .message.error'); | |
if (firstError) { | |
firstError.scrollIntoView({ behavior: 'smooth', block: 'center' }); | |
if (firstError.focus) { | |
firstError.focus(); | |
} | |
} | |
} | |
addFieldError(fieldId, message) { | |
const field = document.getElementById(fieldId); | |
if (!field) return; | |
field.classList.add('error'); | |
// Remove existing error message | |
const existingError = field.parentNode.querySelector('.field-error'); | |
if (existingError) { | |
existingError.remove(); | |
} | |
// Add error message | |
const errorEl = document.createElement('div'); | |
errorEl.className = 'field-error'; | |
errorEl.textContent = message; | |
field.parentNode.appendChild(errorEl); | |
} | |
clearFieldErrors() { | |
document.querySelectorAll('.form-input.error').forEach(field => { | |
field.classList.remove('error'); | |
}); | |
document.querySelectorAll('.field-error').forEach(error => { | |
error.remove(); | |
}); | |
} | |
showUploadProgress(filename, progress) { | |
// This could be expanded for actual progress tracking | |
console.log(`Uploading ${filename}: ${progress}%`); | |
} | |
} | |