185 lines
6.5 KiB
JavaScript
185 lines
6.5 KiB
JavaScript
|
|
export class WaterTracker {
|
|
constructor(containerId) {
|
|
this.container = document.getElementById(containerId);
|
|
this.state = {
|
|
current: 0,
|
|
goal: 3000, // mL
|
|
bottleSize: 500, // mL
|
|
};
|
|
this.STORAGE_KEY = 'hydroflux_data';
|
|
this.loadState();
|
|
this.render();
|
|
this.attachEvents();
|
|
this.updateUI();
|
|
}
|
|
|
|
loadState() {
|
|
const saved = localStorage.getItem(this.STORAGE_KEY);
|
|
if (saved) {
|
|
const parsed = JSON.parse(saved);
|
|
this.state = { ...this.state, ...parsed };
|
|
}
|
|
}
|
|
|
|
saveState() {
|
|
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.state));
|
|
this.updateUI();
|
|
}
|
|
|
|
addWater() {
|
|
this.state.current += this.state.bottleSize;
|
|
this.saveState();
|
|
if (navigator.vibrate) navigator.vibrate(50);
|
|
}
|
|
|
|
removeWater() {
|
|
this.state.current = Math.max(0, this.state.current - this.state.bottleSize);
|
|
this.saveState();
|
|
if (navigator.vibrate) navigator.vibrate(50);
|
|
}
|
|
|
|
setBottleSize(size) {
|
|
if (!size || size <= 0) return;
|
|
this.state.bottleSize = size;
|
|
this.saveState();
|
|
}
|
|
|
|
getPercentage() {
|
|
return Math.min(100, Math.max(0, (this.state.current / this.state.goal) * 100));
|
|
}
|
|
|
|
updateUI() {
|
|
// Update Text
|
|
const currentEl = this.container.querySelector('.water-count');
|
|
const percentageEl = this.container.querySelector('.water-percentage');
|
|
|
|
if (currentEl) currentEl.textContent = `${this.state.current} / ${this.state.goal} mL`;
|
|
if (percentageEl) percentageEl.textContent = `${Math.round(this.getPercentage())}%`;
|
|
|
|
// Update Wave Animation Height
|
|
const wave = this.container.querySelector('.wave');
|
|
if (wave) {
|
|
wave.style.top = `${100 - this.getPercentage()}%`;
|
|
}
|
|
}
|
|
|
|
render() {
|
|
this.container.innerHTML = `
|
|
<div class="water-tracker-container">
|
|
<!-- Circular Progress -->
|
|
<div class="circle-container">
|
|
<div class="water-circle">
|
|
<div class="wave"></div>
|
|
<div class="circle-content">
|
|
<span class="water-percentage">0%</span>
|
|
<span class="water-label">HYDRATION</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats Display -->
|
|
<div class="stats-row">
|
|
<span class="water-count">0 / 3000 mL</span>
|
|
</div>
|
|
|
|
<!-- Controls -->
|
|
<div class="controls-area">
|
|
<div class="bottle-selector">
|
|
<label>Bottle (mL):</label>
|
|
<input type="number" id="bottle-size-input" value="${this.state.bottleSize}" min="1" max="5000">
|
|
</div>
|
|
|
|
<div class="action-buttons">
|
|
<button id="remove-water-btn" class="icon-btn secondary" aria-label="Remove Drink">
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M5 12h14"/>
|
|
</svg>
|
|
</button>
|
|
|
|
<button id="add-water-btn" class="glow-btn">
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M12 5v14M5 12h14"/>
|
|
</svg>
|
|
DRINK
|
|
</button>
|
|
|
|
<!-- Notification Toggle -->
|
|
<button id="notify-btn" class="icon-btn" title="Enable Reminders">
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path>
|
|
<path d="M13.73 21a2 2 0 0 1-3.46 0"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
this.checkNotificationStatus();
|
|
}
|
|
|
|
attachEvents() {
|
|
this.container.querySelector('#add-water-btn').addEventListener('click', () => {
|
|
this.addWater();
|
|
});
|
|
|
|
this.container.querySelector('#remove-water-btn').addEventListener('click', () => {
|
|
this.removeWater();
|
|
});
|
|
|
|
this.container.querySelector('#notify-btn').addEventListener('click', (e) => {
|
|
this.toggleNotifications(e.currentTarget);
|
|
});
|
|
|
|
const input = this.container.querySelector('#bottle-size-input');
|
|
input.addEventListener('change', (e) => {
|
|
this.setBottleSize(parseInt(e.target.value));
|
|
});
|
|
}
|
|
|
|
// --- Notification Logic ---
|
|
|
|
toggleNotifications(btn) {
|
|
if (!("Notification" in window)) {
|
|
alert("This browser does not support desktop notifications");
|
|
return;
|
|
}
|
|
|
|
if (Notification.permission === "granted") {
|
|
alert("Reminders are active! We'll check every hour.");
|
|
} else if (Notification.permission !== "denied") {
|
|
Notification.requestPermission().then(permission => {
|
|
if (permission === "granted") {
|
|
this.startReminderLoop();
|
|
btn.style.color = "var(--primary-cyan)";
|
|
new Notification("HydroFlux", { body: "Smart Hydration Reminders Enabled!" });
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
checkNotificationStatus() {
|
|
if (Notification.permission === "granted") {
|
|
const btn = this.container.querySelector('#notify-btn');
|
|
if (btn) btn.style.color = "var(--primary-cyan)";
|
|
this.startReminderLoop();
|
|
}
|
|
}
|
|
|
|
startReminderLoop() {
|
|
// Clear existing to avoid duplicates
|
|
if (this.reminderInterval) clearInterval(this.reminderInterval);
|
|
|
|
// Check every minute if it's been > 1 hour since last drink
|
|
this.reminderInterval = setInterval(() => {
|
|
// Pseudo-logic check since we don't store timestamp in this simple version yet
|
|
// In a real app, you'd check this.state.lastDrinkTime
|
|
new Notification("HydroFlux Needs You", {
|
|
body: "Remember to drink water!",
|
|
icon: "/icon.png"
|
|
});
|
|
}, 3600000); // 1 Hour
|
|
}
|
|
}
|