import { getSession, login, logout, sendPasswordReset, updatePassword, onAuthChange } from './auth.js'; import { renderDashboard } from './dashboard.js'; import { renderReports, openReportById } from './reports.js'; import { renderSearch } from './search.js'; import { renderSharing } from './sharing.js'; import { renderAccount } from './account.js'; import { renderSettings } from './settings.js'; import { loadCloudReports, refreshFromCloud, getLocalReportsPendingCloud, uploadLocalReportsToCloud } from './storage.js'; const authView = document.querySelector('#auth-view'); const dashboardView = document.querySelector('#dashboard-view'); const authMessage = document.querySelector('#auth-message'); const loginForm = document.querySelector('#login-form'); const resetForm = document.querySelector('#reset-form'); const updatePasswordForm = document.querySelector('#update-password-form'); const showReset = document.querySelector('#show-reset'); const backToLogin = document.querySelector('#back-to-login'); let currentModule = 'dashboard'; const CLOUD_MIGRATION_SKIP_KEY = 'streamline-itp-cloud-migration-skipped'; const AUTO_CLOUD_PULL_INTERVAL_MS = 120000; let autoCloudPullTimer = null; let autoCloudPullInProgress = false; const modules = { dashboard: document.querySelector('#dashboard-module'), reports: document.querySelector('#reports-module'), search: document.querySelector('#search-module'), sharing: document.querySelector('#sharing-module'), account: document.querySelector('#account-module'), settings: document.querySelector('#settings-module') }; init(); async function init() { bindAuthEvents(); bindNavigation(); renderAllModules(); bindAutoCloudSyncEvents(); const isRecovery = window.location.hash.includes('type=recovery') || window.location.search.includes('type=recovery'); const session = await getSession().catch(() => null); if (isRecovery) { showAuth(); showPasswordUpdate(); return; } session ? showApp() : showAuth(); onAuthChange((_event, sessionData) => { if (sessionData && dashboardView.classList.contains('hidden')) showApp(currentModule); if (!sessionData) showAuth(); }); } function renderAllModules() { renderDashboard(modules.dashboard, { onOpenReport: openDashboardReport }); renderReports(modules.reports, { onReportsChanged: refreshDashboardAndSearch }); renderSearch(modules.search); renderSharing(modules.sharing); renderSettings(modules.settings, refreshDashboardAndSearch); } function openDashboardReport(reportId) { const opened = openReportById(reportId); if (opened) showModule('reports'); } function refreshDashboardAndSearch() { renderDashboard(modules.dashboard, { onOpenReport: openDashboardReport }); renderSearch(modules.search); } function bindAuthEvents() { loginForm.addEventListener('submit', async (event) => { event.preventDefault(); setMessage('Logging in...'); try { await login(document.querySelector('#login-email').value, document.querySelector('#login-password').value); setMessage('Login successful.', 'success'); showApp(); } catch (error) { setMessage(error.message, 'error'); } }); resetForm.addEventListener('submit', async (event) => { event.preventDefault(); setMessage('Sending reset link...'); try { await sendPasswordReset(document.querySelector('#reset-email').value); setMessage('Password reset email sent. Check your inbox.', 'success'); } catch (error) { setMessage(error.message, 'error'); } }); updatePasswordForm.addEventListener('submit', async (event) => { event.preventDefault(); setMessage('Updating password...'); try { await updatePassword(document.querySelector('#new-password').value); setMessage('Password updated. You can now use the app.', 'success'); showApp(); } catch (error) { setMessage(error.message, 'error'); } }); showReset.addEventListener('click', () => { loginForm.classList.add('hidden'); resetForm.classList.remove('hidden'); updatePasswordForm.classList.add('hidden'); showReset.classList.add('hidden'); backToLogin.classList.remove('hidden'); setMessage(''); }); backToLogin.addEventListener('click', () => { resetForm.classList.add('hidden'); updatePasswordForm.classList.add('hidden'); loginForm.classList.remove('hidden'); showReset.classList.remove('hidden'); backToLogin.classList.add('hidden'); setMessage(''); }); document.querySelector('#logout-btn').addEventListener('click', async () => { await logout(); showAuth(); }); } function bindNavigation() { document.querySelectorAll('.nav-item[data-module]').forEach((button) => { button.addEventListener('click', () => { showModule(button.dataset.module); closeMobileMenu(); }); }); document.querySelector('#new-report-btn').addEventListener('click', () => showModule('reports')); const menuButton = document.querySelector('#mobile-menu-btn'); const sidebar = document.querySelector('.sidebar'); if (menuButton && sidebar) { menuButton.addEventListener('click', () => { const open = sidebar.classList.toggle('mobile-open'); menuButton.setAttribute('aria-expanded', String(open)); menuButton.textContent = open ? 'Close' : 'Menu'; }); } } function showModule(name) { currentModule = name; Object.entries(modules).forEach(([key, element]) => element.classList.toggle('hidden', key !== name)); document.querySelectorAll('.nav-item[data-module]').forEach((button) => { button.classList.toggle('active', button.dataset.module === name); }); const titles = { dashboard: 'Dashboard', reports: 'ITP Reports', search: 'Search', sharing: 'Sharing', account: 'My Account', settings: 'Settings' }; document.querySelector('#page-title').textContent = titles[name] || 'Dashboard'; if (name === 'account') renderAccount(modules.account, showAuth); if (name === 'dashboard') renderDashboard(modules.dashboard, { onOpenReport: openDashboardReport }); if (name === 'search') renderSearch(modules.search); } function showAuth() { stopAutoCloudPull(); dashboardView.classList.add('hidden'); authView.classList.remove('hidden'); } function showApp(moduleName = currentModule || 'dashboard') { authView.classList.add('hidden'); dashboardView.classList.remove('hidden'); showModule(moduleName); setCloudStatus('Saving/loading cloud reports...', 'saving'); // Full cloud sync: pull saved reports from Supabase onto this device after login. loadCloudReports() .then(async () => { renderAllModules(); showModule(moduleName); setCloudStatus('All changes saved', 'saved'); await maybeShowCloudMigrationPrompt(); startAutoCloudPull(); }) .catch((error) => { console.warn('Cloud sync failed. Local reports are still available.', error?.message || error); setCloudStatus('Offline - changes saved locally', 'offline'); }); } function bindAutoCloudSyncEvents() { window.addEventListener('online', () => runAutoCloudPull('Back online - pulling latest reports...')); window.addEventListener('focus', () => runAutoCloudPull('Checking cloud for latest reports...')); document.addEventListener('visibilitychange', () => { if (!document.hidden) runAutoCloudPull('Checking cloud for latest reports...'); }); } function startAutoCloudPull() { stopAutoCloudPull(); autoCloudPullTimer = window.setInterval(() => { runAutoCloudPull('Checking cloud for latest reports...'); }, AUTO_CLOUD_PULL_INTERVAL_MS); } function stopAutoCloudPull() { if (autoCloudPullTimer) { window.clearInterval(autoCloudPullTimer); autoCloudPullTimer = null; } } async function runAutoCloudPull(statusText = 'Checking cloud for latest reports...') { if (dashboardView.classList.contains('hidden')) return; if (autoCloudPullInProgress) return; if (!navigator.onLine) { setCloudStatus('Offline - changes saved locally', 'offline'); return; } autoCloudPullInProgress = true; setCloudStatus(statusText, 'saving'); try { await refreshFromCloud(); renderAllModules(); showModule(currentModule); setCloudStatus('All changes saved', 'saved'); } catch (error) { console.warn('Automatic cloud pull failed.', error?.message || error); setCloudStatus('Offline - changes saved locally', 'offline'); } finally { autoCloudPullInProgress = false; } } async function maybeShowCloudMigrationPrompt() { if (localStorage.getItem(CLOUD_MIGRATION_SKIP_KEY) === 'true') return; try { const pendingReports = await getLocalReportsPendingCloud(); if (!pendingReports.length) return; showCloudMigrationModal(pendingReports.length); } catch (error) { console.warn('Cloud migration prompt skipped.', error?.message || error); } } function showCloudMigrationModal(reportCount) { const existing = document.querySelector('#cloud-migration-modal'); if (existing) existing.remove(); const modal = document.createElement('div'); modal.id = 'cloud-migration-modal'; modal.className = 'cloud-modal'; modal.innerHTML = ` `; document.body.appendChild(modal); modal.querySelector('#cloud-skip-now').addEventListener('click', () => { localStorage.setItem(CLOUD_MIGRATION_SKIP_KEY, 'true'); modal.remove(); setCloudStatus('Cloud sync skipped - upload available in Settings', 'offline'); }); modal.querySelector('#cloud-upload-all').addEventListener('click', async () => { await runCloudMigration(modal); }); } async function runCloudMigration(modal) { const progressWrap = modal.querySelector('.cloud-progress'); const progressBar = modal.querySelector('.cloud-progress-bar span'); const progressText = modal.querySelector('.cloud-progress-text'); const actions = modal.querySelector('.cloud-modal-actions'); progressWrap.classList.remove('hidden'); actions.classList.add('hidden'); setCloudStatus('Uploading local reports...', 'saving'); try { const result = await uploadLocalReportsToCloud(({ uploaded, total, current }) => { const percent = total ? Math.round((uploaded / total) * 100) : 100; progressBar.style.width = `${percent}%`; progressText.textContent = `Uploading ${Math.min(uploaded + 1, total)} / ${total}: ${current?.projectName || 'Untitled ITP Report'}`; }); localStorage.removeItem(CLOUD_MIGRATION_SKIP_KEY); progressBar.style.width = '100%'; progressText.textContent = `${result.uploaded} report${result.uploaded === 1 ? '' : 's'} uploaded successfully.`; setCloudStatus('All changes saved', 'saved'); renderAllModules(); showModule(currentModule); setTimeout(() => { modal.remove(); }, 1200); } catch (error) { progressText.textContent = error?.message || 'Upload failed. Please try again from Settings.'; setCloudStatus('Cloud upload failed - local backup retained', 'offline'); actions.classList.remove('hidden'); } } function setCloudStatus(text, state = 'saved') { const status = document.querySelector('#cloud-status'); if (!status) return; status.textContent = text; status.className = `cloud-status ${state}`.trim(); } window.streamlineItpUploadLocalReportsToCloud = async function manualCloudUploadFromSettings() { const pending = await getLocalReportsPendingCloud(); if (!pending.length) return { uploaded: 0, total: 0 }; return uploadLocalReportsToCloud(); }; function closeMobileMenu() { const sidebar = document.querySelector('.sidebar'); const menuButton = document.querySelector('#mobile-menu-btn'); if (sidebar) sidebar.classList.remove('mobile-open'); if (menuButton) { menuButton.setAttribute('aria-expanded', 'false'); menuButton.textContent = 'Menu'; } } function showPasswordUpdate() { loginForm.classList.add('hidden'); resetForm.classList.add('hidden'); updatePasswordForm.classList.remove('hidden'); showReset.classList.add('hidden'); backToLogin.classList.remove('hidden'); setMessage('Enter a new password to complete recovery.'); } function setMessage(message, type = '') { authMessage.textContent = message; authMessage.className = `message ${type}`.trim(); }