/* Mobile JavaScript for Seismo Fleet Manager */
/* Handles hamburger menu, modals, offline sync, and mobile interactions */
// ===== GLOBAL STATE =====
let currentUnitData = null;
let isOnline = navigator.onLine;
// ===== HAMBURGER MENU TOGGLE =====
function toggleMenu() {
const sidebar = document.getElementById('sidebar');
const backdrop = document.getElementById('backdrop');
const hamburgerBtn = document.getElementById('hamburgerBtn');
if (sidebar && backdrop) {
const isOpen = sidebar.classList.contains('open');
if (isOpen) {
// Close menu
sidebar.classList.remove('open');
backdrop.classList.remove('show');
hamburgerBtn?.classList.remove('menu-open');
document.body.style.overflow = '';
} else {
// Open menu
sidebar.classList.add('open');
backdrop.classList.add('show');
hamburgerBtn?.classList.add('menu-open');
document.body.style.overflow = 'hidden';
}
}
}
// Close menu when clicking backdrop
function closeMenuFromBackdrop() {
const sidebar = document.getElementById('sidebar');
const backdrop = document.getElementById('backdrop');
const hamburgerBtn = document.getElementById('hamburgerBtn');
if (sidebar && backdrop) {
sidebar.classList.remove('open');
backdrop.classList.remove('show');
hamburgerBtn?.classList.remove('menu-open');
document.body.style.overflow = '';
}
}
// Close menu when window is resized to desktop
function handleResize() {
if (window.innerWidth >= 768) {
const sidebar = document.getElementById('sidebar');
const backdrop = document.getElementById('backdrop');
const hamburgerBtn = document.getElementById('hamburgerBtn');
if (sidebar && backdrop) {
sidebar.classList.remove('open');
backdrop.classList.remove('show');
hamburgerBtn?.classList.remove('menu-open');
document.body.style.overflow = '';
}
}
}
// ===== UNIT DETAIL MODAL =====
function openUnitModal(unitId, status = null, age = null) {
const modal = document.getElementById('unitModal');
if (!modal) return;
// Store the status info passed from the card
// Accept status if it's a non-empty string, use age if provided or default to '--'
const cardStatusInfo = (status && status !== '') ? {
status: status,
age: age || '--'
} : null;
console.log('openUnitModal:', { unitId, status, age, cardStatusInfo });
// Fetch unit data and populate modal
fetchUnitDetails(unitId).then(unit => {
if (unit) {
currentUnitData = unit;
// Pass the card status info to the populate function
populateUnitModal(unit, cardStatusInfo);
modal.classList.add('show');
document.body.style.overflow = 'hidden';
}
});
}
function closeUnitModal(event) {
// Only close if clicking backdrop or close button
if (event && event.target.closest('.unit-modal-content') && !event.target.closest('[data-close-modal]')) {
return;
}
const modal = document.getElementById('unitModal');
if (modal) {
modal.classList.remove('show');
document.body.style.overflow = '';
currentUnitData = null;
}
}
async function fetchUnitDetails(unitId) {
try {
// Try to fetch from network first
const response = await fetch(`/api/roster/${unitId}`);
if (response.ok) {
const unit = await response.json();
// Save to IndexedDB if offline support is available
if (window.offlineDB) {
await window.offlineDB.saveUnit(unit);
}
return unit;
}
} catch (error) {
console.log('Network fetch failed, trying offline storage:', error);
// Fall back to offline storage
if (window.offlineDB) {
return await window.offlineDB.getUnit(unitId);
}
}
return null;
}
function populateUnitModal(unit, cardStatusInfo = null) {
// Set unit ID in header
const modalUnitId = document.getElementById('modalUnitId');
if (modalUnitId) {
modalUnitId.textContent = unit.id;
}
// Populate modal content
const modalContent = document.getElementById('modalContent');
if (!modalContent) return;
// Use status from card if provided, otherwise get from snapshot or derive from unit
let statusInfo = cardStatusInfo || getUnitStatus(unit.id, unit);
console.log('populateUnitModal:', { unit, cardStatusInfo, statusInfo });
const statusColor = statusInfo.status === 'OK' ? 'green' :
statusInfo.status === 'Pending' ? 'yellow' :
statusInfo.status === 'Missing' ? 'red' : 'gray';
const statusTextColor = statusInfo.status === 'OK' ? 'text-green-600 dark:text-green-400' :
statusInfo.status === 'Pending' ? 'text-yellow-600 dark:text-yellow-400' :
statusInfo.status === 'Missing' ? 'text-red-600 dark:text-red-400' :
'text-gray-600 dark:text-gray-400';
// Determine status label (show "Benched" instead of "Unknown" for non-deployed units)
let statusLabel = statusInfo.status;
if ((statusInfo.status === 'Unknown' || statusInfo.status === 'N/A') && !unit.deployed) {
statusLabel = 'Benched';
}
// Create navigation URL for location
const createNavUrl = (address, coordinates) => {
if (address) {
// Use address for navigation
const encodedAddress = encodeURIComponent(address);
// Universal link that works on iOS and Android
return `https://www.google.com/maps/search/?api=1&query=${encodedAddress}`;
} else if (coordinates) {
// Use coordinates for navigation (format: lat,lon)
const encodedCoords = encodeURIComponent(coordinates);
return `https://www.google.com/maps/search/?api=1&query=${encodedCoords}`;
}
return null;
};
const navUrl = createNavUrl(unit.address, unit.coordinates);
modalContent.innerHTML = `