TreeTrack / static /js /modules /ui-manager.js
RoyAalekh's picture
perf(ui): preconnect/prefetch map assets, client-side sort by created_at; annotate Ishita’s today entries; defer heavy map work
7db38a6
raw
history blame
8.27 kB
/**
* 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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
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}%`);
}
}