export class Dashboard { constructor(containerId, app) { this.container = document.getElementById(containerId); this.app = app; this.render(); this.attachEvents(); this.startTimers(); } render() { if (!this.container) return; // Data Retrieval (Keeping persistence) const waterData = JSON.parse(localStorage.getItem('hydroflux_data') || '{"current":0,"goal":3.0}'); // Real Health Data (Default to 0 until synced) const healthData = JSON.parse(localStorage.getItem('hydroflux_health_data') || '{"steps": 0, "sleep": 0}'); const stepsData = healthData.steps; const sleepHoursTotal = healthData.sleep; const sleepHours = Math.floor(sleepHoursTotal); const sleepMins = Math.round((sleepHoursTotal - sleepHours) * 60); const goalData = 10000; const goalsData = JSON.parse(localStorage.getItem('hydroflux_goals') || JSON.stringify([ { id: '1', text: 'Drink 3L of water', completed: true }, { id: '2', text: 'Walk 10K steps', completed: false }, { id: '3', text: 'Sleep 8 hours', completed: false }, ])); const currentNote = localStorage.getItem('hydroflux_note_content') || 'Feeling hydrated today. Remember to add those gym sessions!'; // HTML from Single_File.html.txt (Body Content Only) // Adapted to use Template Literals for dynamic data this.container.innerHTML = `
JD
Level 14

Hydro Flux

14 Days
${parseFloat(waterData.current).toFixed(1)}L / ${parseFloat(waterData.goal).toFixed(1)}L

Steps Tracker

${stepsData.toLocaleString()} steps

Strava Integration

S
5.2km Run Last Sync: 2h ago

Sleep Tracker

${sleepHours}h ${sleepMins}m Light Sleep

Goals

Notes

`; this.updateDynamicElements(stepsData, goalData); this.renderGoals(goalsData); // Expose Native Callback window.updateHealthData = (steps, sleepHoursTotal) => { console.log("Received Health Data:", steps, sleepHoursTotal); // Save to Storage localStorage.setItem('hydroflux_health_data', JSON.stringify({ steps: steps, sleep: sleepHoursTotal })); // Update UI directly const stepsCount = this.container.querySelector('#stepsCount'); if (stepsCount) stepsCount.textContent = steps.toLocaleString(); const sh = Math.floor(sleepHoursTotal); const sm = Math.round((sleepHoursTotal - sh) * 60); const sleepTime = this.container.querySelector('#sleepTime'); if (sleepTime) sleepTime.textContent = `${sh}h ${sm}m`; this.updateDynamicElements(steps, 10000); }; // Trigger initial sync if native interface exists if (window.HydroFluxNative) { try { window.HydroFluxNative.requestHealthPermissions(); } catch (e) { console.error("Native sync error", e); } } } updateDynamicElements(steps, goal) { // Steps Circle Logic const percentage = Math.min((steps / goal) * 100, 100); const circumference = 2 * Math.PI * 45; const strokeDashoffset = circumference - (percentage / 100) * circumference; const circle = this.container.querySelector('#stepsCircle'); if (circle) { circle.style.strokeDasharray = circumference; circle.style.strokeDashoffset = strokeDashoffset; } // Sleep Chart Logic (Static for now, but could be dynamic) const sleepData = [30, 35, 25, 40, 55, 45, 60, 75, 70, 80, 65, 55, 50, 45, 40]; const chartWidth = 300; const chartHeight = 100; const points = sleepData.map((value, index) => { const x = (index / (sleepData.length - 1)) * chartWidth; const y = chartHeight - (value / 100) * chartHeight; return x + ',' + y; }).join(' '); const line = this.container.querySelector('#sleepLine'); if (line) line.setAttribute('points', points); } renderGoals(goalsData) { const list = this.container.querySelector('#goalsList'); if (!list) return; list.innerHTML = goalsData.map(g => ` `).join(''); // Re-attach listeners for goals list.querySelectorAll('.goal-btn').forEach(btn => { btn.addEventListener('click', () => { const id = btn.dataset.id; const goal = goalsData.find(g => g.id === id); if (goal) { goal.completed = !goal.completed; localStorage.setItem('hydroflux_goals', JSON.stringify(goalsData)); this.renderGoals(goalsData); // Re-render goals only } }); }); } attachEvents() { const self = this; // Capture this // Navigation Events (Widget Clicks) // Water - Targeting the wrapper div mostly, ensuring specific targets don't block const waterCard = this.container.querySelector('.water-card-n'); if (waterCard) { waterCard.style.cursor = 'pointer'; waterCard.addEventListener('click', (e) => { // IMPORTANT: Do NOT navigate if clicking the 'Add Water' button or its SVG children if (e.target.closest('#addWaterBtn')) { // Do nothing, the add listener below handles it return; } this.app.navigateTo('water-detail'); }); } // Notes const notesCard = this.container.querySelector('#notesInput')?.closest('.rounded-2xl'); if (notesCard) { // Make the whole card clickable, but maybe not the textarea itself if we want to allow quick edits? // User requested "click on notes we get a fullscreen pop", so presumably we redirect immediately. // Or we make the header/container clickable. // Let's make the textarea readonly in dashboard view OR just redirect on focus? // "click on notes we get a fullscreen pop" -> redirect on click anywhere on card const ta = this.container.querySelector('#notesInput'); if (ta) ta.setAttribute('readonly', 'true'); // Make it read-only on dashboard to force click-through notesCard.style.cursor = 'pointer'; notesCard.addEventListener('click', () => { this.app.navigateTo('notes-detail'); }); } const stepsCard = this.container.querySelector('#stepsCircle')?.closest('.rounded-2xl'); if (stepsCard) { stepsCard.style.cursor = 'pointer'; stepsCard.addEventListener('click', () => this.app.navigateTo('fitness-detail')); } // Strava const stravaCard = this.container.querySelector('.from-orange-50'); if (stravaCard) { stravaCard.style.cursor = 'pointer'; stravaCard.addEventListener('click', () => this.app.navigateTo('fitness-detail')); } // Sleep const sleepCard = this.container.querySelector('#sleepChart')?.closest('.rounded-2xl'); if (sleepCard) { sleepCard.style.cursor = 'pointer'; sleepCard.addEventListener('click', () => this.app.navigateTo('sleep-detail')); } // Goals const goalsCard = this.container.querySelector('#goalsList')?.closest('.rounded-2xl'); if (goalsCard) { // Only navigate if not clicking a specialized button, but goals list is buttons... // Maybe we add a "View All" or just make the header clickable? // For now, let's make the header clickable const header = goalsCard.querySelector('h3'); if (header) { header.innerHTML += ' View All →'; header.addEventListener('click', () => this.app.navigateTo('goals-detail')); } } // Water Add const addBtn = this.container.querySelector('#addWaterBtn'); if (addBtn) { addBtn.addEventListener('click', (e) => { e.stopPropagation(); // Stop bubble to card click // Use stored drink size or default to 250ml const drinkSize = parseInt(localStorage.getItem('hydroflux_drink_size') || '250'); const drinkLitres = drinkSize / 1000; const data = JSON.parse(localStorage.getItem('hydroflux_data') || '{"current":0,"goal":3.0}'); if (data.current < data.goal) { data.current = Math.min(data.current + drinkLitres, data.goal); localStorage.setItem('hydroflux_data', JSON.stringify(data)); // Add to history (to sync with detail view) const history = JSON.parse(localStorage.getItem('hydroflux_water_history') || '[]'); history.push({ timestamp: Date.now(), amount: drinkSize }); localStorage.setItem('hydroflux_water_history', JSON.stringify(history)); // Update Display without full re-render const percentage = (data.current / data.goal) * 100; const fill = this.container.querySelector('#waterFill'); const amount = this.container.querySelector('#waterAmount'); if (fill) fill.style.height = percentage + '%'; if (amount) amount.textContent = data.current.toFixed(1) + 'L / ' + data.goal.toFixed(1) + 'L'; } }); } // Sync Health Listener const syncBtn = this.container.querySelector('#syncHealthBtn'); if (syncBtn) { syncBtn.addEventListener('click', () => { if (window.HydroFluxNative) { window.HydroFluxNative.requestHealthPermissions(); } else { alert("Health Sync only available on Android App"); // Mock data for browser testing window.updateHealthData(12500, 7.8); } }); } // Notes Save const noteArea = this.container.querySelector('#notesInput'); if (noteArea) { noteArea.addEventListener('input', (e) => { localStorage.setItem('hydroflux_note_content', e.target.value); }); } } startTimers() { const updateTime = () => { const now = new Date(); const dateEl = this.container.querySelector('#currentDate'); const timeEl = this.container.querySelector('#currentTime'); if (dateEl) dateEl.textContent = now.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); if (timeEl) timeEl.textContent = now.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }) + ' AEDT'; }; updateTime(); setInterval(updateTime, 1000); } }