Add map/table toggle view for current reports

 New Features:
- Toggle between map and table view for current reports
- Table view shows location, details, reported time, and time remaining
- Color-coded time remaining: urgent (red), warning (orange), normal (green)
- Responsive design with mobile-optimized table layout
- Real-time updates work in both map and table views
- Sorted by most recent reports first

🎨 UI Improvements:
- Professional toggle buttons with active state
- Clean table design with hover effects
- Accessibility-friendly with proper titles and tooltips
- Mobile-responsive layout adjustments

🚀 Better UX:
- Easy switching between visual map and detailed table
- Time remaining countdown helps prioritize urgent reports
- Searchable and scannable table format for quick review
- Maintains all existing functionality while adding new view
This commit is contained in:
Deco Vander 2025-07-03 01:07:17 -04:00
parent 3581ea219d
commit 5e56d59bbd
3 changed files with 334 additions and 7 deletions

View file

@ -23,11 +23,21 @@ document.addEventListener('DOMContentLoaded', async () => {
const addressInput = document.getElementById('address');
const autocompleteList = document.getElementById('autocomplete-list');
// View toggle elements
const mapViewBtn = document.getElementById('map-view-btn');
const tableViewBtn = document.getElementById('table-view-btn');
const mapView = document.getElementById('map-view');
const tableView = document.getElementById('table-view');
const reportsTableBody = document.getElementById('reports-tbody');
const tableLocationCount = document.getElementById('table-location-count');
let autocompleteTimeout;
let selectedIndex = -1;
let autocompleteResults = [];
let currentMarkers = [];
let updateInterval;
let currentLocations = [];
let currentView = 'map'; // 'map' or 'table'
const clearMarkers = () => {
currentMarkers.forEach(marker => {
@ -64,14 +74,112 @@ document.addEventListener('DOMContentLoaded', async () => {
return 'over a day ago';
};
// Toggle between map and table view
const switchView = (viewType) => {
currentView = viewType;
if (viewType === 'map') {
mapView.style.display = 'block';
tableView.style.display = 'none';
mapViewBtn.classList.add('active');
tableViewBtn.classList.remove('active');
// Invalidate map size after showing
setTimeout(() => map.invalidateSize(), 100);
} else {
mapView.style.display = 'none';
tableView.style.display = 'block';
mapViewBtn.classList.remove('active');
tableViewBtn.classList.add('active');
// Render table with current data
renderTable(currentLocations);
}
};
// Render table view
const renderTable = (locations) => {
if (!locations || locations.length === 0) {
reportsTableBody.innerHTML = '<tr><td colspan="4" class="loading">No active reports</td></tr>';
tableLocationCount.textContent = '0 active reports';
return;
}
// Sort by most recent first
const sortedLocations = [...locations].sort((a, b) =>
new Date(b.created_at) - new Date(a.created_at)
);
const tableHTML = sortedLocations.map(location => {
const timeAgo = getTimeAgo(location.created_at);
const timeRemaining = getTimeRemaining(location.created_at);
const remainingClass = getRemainingClass(location.created_at);
const reportedTime = new Date(location.created_at).toLocaleString();
return `
<tr>
<td class="location-cell" title="${location.address}">${location.address}</td>
<td class="details-cell" title="${location.description || 'No additional details'}">
${location.description || '<em>No additional details</em>'}
</td>
<td class="time-cell" title="${reportedTime}">${timeAgo}</td>
<td class="remaining-cell ${remainingClass}">${timeRemaining}</td>
</tr>
`;
}).join('');
reportsTableBody.innerHTML = tableHTML;
tableLocationCount.textContent = `${locations.length} active report${locations.length !== 1 ? 's' : ''}`;
};
// Calculate time remaining until 24-hour expiration
const getTimeRemaining = (timestamp) => {
const now = new Date();
const reportTime = new Date(timestamp);
const expirationTime = new Date(reportTime.getTime() + 24 * 60 * 60 * 1000);
const remaining = expirationTime - now;
if (remaining <= 0) return 'Expired';
const hours = Math.floor(remaining / (1000 * 60 * 60));
const minutes = Math.floor((remaining % (1000 * 60 * 60)) / (1000 * 60));
if (hours > 0) {
return `${hours}h ${minutes}m`;
} else {
return `${minutes}m`;
}
};
// Get CSS class for time remaining
const getRemainingClass = (timestamp) => {
const now = new Date();
const reportTime = new Date(timestamp);
const expirationTime = new Date(reportTime.getTime() + 24 * 60 * 60 * 1000);
const remaining = expirationTime - now;
const hoursRemaining = remaining / (1000 * 60 * 60);
if (hoursRemaining <= 1) return 'urgent';
if (hoursRemaining <= 6) return 'warning';
return 'normal';
};
const refreshLocations = () => {
fetch('/api/locations')
.then(res => res.json())
.then(locations => {
currentLocations = locations;
// Update map view
showMarkers(locations);
const countElement = document.getElementById('location-count');
countElement.textContent = `${locations.length} active report${locations.length !== 1 ? 's' : ''}`;
// Update table view if it's currently visible
if (currentView === 'table') {
renderTable(locations);
}
const now = new Date();
const timeStr = now.toLocaleTimeString('en-US', {
hour12: false,
@ -84,6 +192,9 @@ document.addEventListener('DOMContentLoaded', async () => {
.catch(err => {
console.error('Error fetching locations:', err);
document.getElementById('location-count').textContent = 'Error loading locations';
if (currentView === 'table') {
reportsTableBody.innerHTML = '<tr><td colspan="4" class="loading">Error loading reports</td></tr>';
}
});
};
@ -423,6 +534,10 @@ document.addEventListener('DOMContentLoaded', async () => {
}
});
// View toggle event listeners
mapViewBtn.addEventListener('click', () => switchView('map'));
tableViewBtn.addEventListener('click', () => switchView('table'));
window.addEventListener('beforeunload', () => {
if (updateInterval) {
clearInterval(updateInterval);