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 = `
${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
`;
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);
}
}