// Personal Dashboard JavaScript with HTMX Integration // Constants const AUTO_REFRESH_INTERVAL = 5 * 60 * 1000; // 5 minutes in milliseconds // Get CSRF token from body hx-headers attribute function getCSRFToken() { const body = document.body; const headers = body.getAttribute('hx-headers'); if (headers) { try { const parsed = JSON.parse(headers); return parsed['X-CSRF-Token'] || ''; } catch (e) { console.error('Failed to parse CSRF token:', e); } } return ''; } // Track current active tab (read from URL for state persistence) const urlParams = new URLSearchParams(window.location.search); let currentTab = urlParams.get('tab') || 'tasks'; let autoRefreshTimer = null; // Initialize on page load document.addEventListener('DOMContentLoaded', function() { console.log('Dashboard initialized'); // Set up HTMX event listeners setupHtmxListeners(); // Start auto-refresh startAutoRefresh(); }); // HTMX Event Listeners function setupHtmxListeners() { // Before HTMX request document.body.addEventListener('htmx:beforeRequest', function(evt) { const target = evt.detail.target; if (target.id === 'tab-content') { // Show loading state target.classList.add('opacity-50', 'pointer-events-none'); } }); // After HTMX request completes document.body.addEventListener('htmx:afterRequest', function(evt) { const target = evt.detail.target; if (target.id === 'tab-content') { // Hide loading state target.classList.remove('opacity-50', 'pointer-events-none'); // Update timestamp updateLastUpdatedTime(); } }); // Handle HTMX errors document.body.addEventListener('htmx:responseError', function(evt) { console.error('HTMX request failed:', evt.detail); alert('Failed to load content. Please try again.'); // Remove loading state const target = evt.detail.target; if (target) { target.classList.remove('opacity-50', 'pointer-events-none'); } }); } // Tab Management function setActiveTab(button) { // Remove active class from all tabs document.querySelectorAll('.tab-button').forEach(tab => { tab.classList.remove('tab-button-active'); }); // Add active class to clicked tab button.classList.add('tab-button-active'); // Extract tab name from hx-get attribute const endpoint = button.getAttribute('hx-get'); currentTab = endpoint.split('/').pop(); // "tasks" or "notes" console.log('Switched to tab:', currentTab); // Reset auto-refresh timer when switching tabs resetAutoRefresh(); } // Manual Refresh async function refreshData() { const button = event.target.closest('button'); const icon = button.querySelector('svg'); // Show loading state button.disabled = true; if (icon) icon.classList.add('animate-spin'); try { // Force API refresh (updates cache) const refreshResponse = await fetch('/api/refresh', { method: 'POST', headers: { 'X-CSRF-Token': getCSRFToken() } }); if (!refreshResponse.ok) throw new Error('Refresh failed'); // Reload current tab from cache const tabResponse = await fetch(`/tabs/${currentTab}`); if (!tabResponse.ok) throw new Error('Tab reload failed'); // Get HTML response and update tab content const html = await tabResponse.text(); const tabContent = document.getElementById('tab-content'); tabContent.innerHTML = html; htmx.process(tabContent); // Update timestamp updateLastUpdatedTime(); // Reset auto-refresh timer resetAutoRefresh(); console.log('Manual refresh successful'); } catch (error) { console.error('Refresh failed:', error); alert('Failed to refresh data. Please try again.'); } finally { // Restore button state button.disabled = false; if (icon) icon.classList.remove('animate-spin'); } } // Auto-refresh Functions function startAutoRefresh() { if (autoRefreshTimer) { clearInterval(autoRefreshTimer); } autoRefreshTimer = setInterval(autoRefresh, AUTO_REFRESH_INTERVAL); console.log('Auto-refresh started (5 min interval)'); } function resetAutoRefresh() { clearInterval(autoRefreshTimer); startAutoRefresh(); } async function autoRefresh() { console.log(`Auto-refreshing ${currentTab} tab...`); try { // Force API refresh (updates cache) const refreshResponse = await fetch('/api/refresh', { method: 'POST', headers: { 'X-CSRF-Token': getCSRFToken() } }); if (!refreshResponse.ok) throw new Error('Refresh failed'); // Reload current tab from cache const tabResponse = await fetch(`/tabs/${currentTab}`); if (tabResponse.ok) { const html = await tabResponse.text(); const tabContent = document.getElementById('tab-content'); tabContent.innerHTML = html; htmx.process(tabContent); updateLastUpdatedTime(); console.log('Auto-refresh successful'); } } catch (error) { console.error('Auto-refresh failed:', error); } } // Update Last Updated Time function updateLastUpdatedTime() { const now = new Date(); const timeString = now.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }); const element = document.getElementById('last-updated'); if (element) { element.textContent = timeString; } } // Filter tasks by status (Phase 2 feature) function filterTasks(status) { console.log('Filter tasks:', status); // To be implemented in Phase 2 } // Toggle task completion (Phase 2 feature) function toggleTask(taskId) { console.log('Toggle task:', taskId); // To be implemented in Phase 2 } // Mobile Swipe Navigation (function() { let touchStartX = 0; let touchEndX = 0; const SWIPE_THRESHOLD = 50; // Minimum px for a swipe // Get ordered list of tab names (main tabs only for swipe) const TAB_ORDER = ['timeline', 'shopping', 'conditions', 'tasks', 'planning', 'meals']; function handleSwipe() { const swipeDistance = touchEndX - touchStartX; if (Math.abs(swipeDistance) < SWIPE_THRESHOLD) { return; // Not a significant swipe } const currentIndex = TAB_ORDER.indexOf(currentTab); if (currentIndex === -1) return; let newIndex; if (swipeDistance > 0) { // Swiped right -> previous tab newIndex = currentIndex - 1; } else { // Swiped left -> next tab newIndex = currentIndex + 1; } // Bounds check if (newIndex < 0 || newIndex >= TAB_ORDER.length) { return; } const newTab = TAB_ORDER[newIndex]; const tabButton = document.querySelector(`[hx-get="/tabs/${newTab}"]`); if (tabButton) { console.log(`Swipe navigation: ${currentTab} -> ${newTab}`); tabButton.click(); } } // Set up touch event listeners on the tab content area document.addEventListener('DOMContentLoaded', function() { const tabContent = document.getElementById('tab-content'); if (!tabContent) return; tabContent.addEventListener('touchstart', function(e) { touchStartX = e.changedTouches[0].screenX; }, { passive: true }); tabContent.addEventListener('touchend', function(e) { touchEndX = e.changedTouches[0].screenX; handleSwipe(); }, { passive: true }); }); })();