whoopfit-tracker / script.js
CSNET's picture
ทำเว็บแอพ html5 หนึ่งหน้าที่มี Feature ใช้งานได้จริงทั้งหมดตามแอพ Whoop version ล่าสุด เอาหน้าจอทุกอย่างเหมือนเป๊ะเลยนะ
b291c07 verified
Raw
History Blame Contribute Delete
12.3 kB
// Initialize app
let currentView = 'dashboardView';
let selectedMood = null;
let charts = {};
// View management
function showView(viewId) {
// Hide all views
document.querySelectorAll('.view').forEach(view => {
view.classList.add('hidden');
});
// Show selected view
document.getElementById(viewId).classList.remove('hidden');
// Update navigation
document.querySelectorAll('.nav-btn').forEach(btn => {
btn.classList.remove('active');
if (btn.dataset.view === viewId) {
btn.classList.add('active');
}
});
currentView = viewId;
// Initialize charts for specific views
if (viewId === 'strainView') {
initializeStrainCharts();
} else if (viewId === 'sleepView') {
initializeSleepCharts();
} else if (viewId === 'recoveryView') {
initializeRecoveryChart();
}
// Re-initialize feather icons
feather.replace();
}
function showJournal() {
showView('journalView');
}
function showProfile() {
// Placeholder for profile modal
alert('Profile feature coming soon!');
}
// Initialize charts
function initializeStrainCharts() {
// Strain Gauge
const strainGaugeCtx = document.getElementById('strainGauge');
if (strainGaugeCtx && !charts.strainGauge) {
charts.strainGauge = new Chart(strainGaugeCtx, {
type: 'doughnut',
data: {
datasets: [{
data: [41, 59],
backgroundColor: ['#eab308', '#fef3c7'],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '70%',
plugins: {
legend: { display: false },
tooltip: { enabled: false }
}
}
});
}
// Hourly Strain Chart
const hourlyStrainCtx = document.getElementById('hourlyStrainChart');
if (hourlyStrainCtx && !charts.hourlyStrain) {
const hours = Array.from({length: 24}, (_, i) => `${i}:00`);
const strainData = [
2, 2, 1, 1, 2, 3, 8, 12, 5, 4, 3, 5, 8, 6, 4, 3, 4, 7, 3, 2, 2, 2, 1, 1
];
charts.hourlyStrain = new Chart(hourlyStrainCtx, {
type: 'line',
data: {
labels: hours,
datasets: [{
label: 'Strain',
data: strainData,
borderColor: '#eab308',
backgroundColor: 'rgba(234, 179, 8, 0.1)',
fill: true,
tension: 0.4,
pointRadius: 2,
pointHoverRadius: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
max: 21,
grid: {
color: 'rgba(0, 0, 0, 0.05)'
}
},
x: {
grid: {
display: false
}
}
},
plugins: {
legend: { display: false }
}
}
});
}
}
function initializeSleepCharts() {
// Sleep Stages Chart
const sleepStagesCtx = document.getElementById('sleepStagesChart');
if (sleepStagesCtx && !charts.sleepStages) {
const timeLabels = Array.from({length: 24}, (_, i) => {
const hour = i < 10 ? `0${i}:00` : `${i}:00`;
return hour;
});
const sleepData = timeLabels.map((_, i) => {
if (i < 22 || i > 23) return 0;
if (i === 22) return 2;
if (i === 23) return 3;
return i % 4 + 1;
});
charts.sleepStages = new Chart(sleepStagesCtx, {
type: 'bar',
data: {
labels: timeLabels,
datasets: [{
label: 'Sleep Stage',
data: sleepData,
backgroundColor: sleepData.map(value => {
if (value === 0) return '#e5e7eb';
if (value === 1) return '#ddd6fe';
if (value === 2) return '#93c5fd';
if (value === 3) return '#60a5fa';
return '#8b5cf6';
}),
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
display: false,
max: 4
},
x: {
ticks: {
maxTicksLimit: 12
},
grid: {
display: false
}
}
},
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: function(context) {
const stages = ['', 'Light Sleep', 'Deep Sleep', 'REM Sleep', 'Awake'];
return stages[context.parsed.y] || 'Awake';
}
}
}
}
}
});
}
}
function initializeRecoveryChart() {
// Recovery Ring Chart
const recoveryRingCtx = document.getElementById('recoveryRing');
if (recoveryRingCtx && !charts.recoveryRing) {
charts.recoveryRing = new Chart(recoveryRingCtx, {
type: 'doughnut',
data: {
datasets: [{
data: [78, 22],
backgroundColor: ['#10b981', '#d1fae5'],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '75%',
plugins: {
legend: { display: false },
tooltip: { enabled: false }
}
}
});
}
}
// Journal functionality
document.addEventListener('DOMContentLoaded', function() {
// Mood buttons
document.querySelectorAll('.mood-btn').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.mood-btn').forEach(b => b.classList.remove('selected'));
this.classList.add('selected');
selectedMood = parseInt(this.dataset.mood);
});
});
// Energy slider
const energySlider = document.getElementById('energyLevel');
const energyValue = document.getElementById('energyValue');
if (energySlider && energyValue) {
energySlider.addEventListener('input', function() {
energyValue.textContent = this.value;
});
}
// Stress slider
const stressSlider = document.getElementById('stressLevel');
const stressValue = document.getElementById('stressValue');
if (stressSlider && stressValue) {
stressSlider.addEventListener('input', function() {
stressValue.textContent = this.value;
});
}
// Journal form submission
const journalForm = document.getElementById('journalForm');
if (journalForm) {
journalForm.addEventListener('submit', function(e) {
e.preventDefault();
const formData = {
mood: selectedMood,
energy: document.getElementById('energyLevel').value,
stress: document.getElementById('stressLevel').value,
activities: Array.from(document.querySelectorAll('.activity-checkbox:checked')).map(cb => cb.value),
nutrition: Array.from(document.querySelectorAll('.nutrition-checkbox:checked')).map(cb => cb.value),
notes: document.getElementById('journalNotes').value,
timestamp: new Date().toISOString()
};
// Save to localStorage (in real app, would send to server)
let journalEntries = JSON.parse(localStorage.getItem('journalEntries') || '[]');
journalEntries.push(formData);
localStorage.setItem('journalEntries', JSON.stringify(journalEntries));
// Show success message
showNotification('Journal entry saved successfully!');
// Reset form
journalForm.reset();
document.querySelectorAll('.mood-btn').forEach(b => b.classList.remove('selected'));
selectedMood = null;
// Go back to dashboard after a short delay
setTimeout(() => showView('dashboardView'), 1500);
});
}
});
// Notification system
function showNotification(message, type = 'success') {
const notification = document.createElement('div');
notification.className = `fixed top-20 left-4 right-4 max-w-md mx-auto px-4 py-3 rounded-lg shadow-lg z-50 transform transition-all duration-300 ${
type === 'success' ? 'bg-emerald-500 text-white' : 'bg-red-500 text-white'
}`;
notification.innerHTML = `
<div class="flex items-center space-x-2">
<i data-feather="${type === 'success' ? 'check-circle' : 'alert-circle'}" class="w-5 h-5"></i>
<span>${message}</span>
</div>
`;
document.body.appendChild(notification);
feather.replace();
// Animate in
setTimeout(() => {
notification.classList.add('translate-y-0');
}, 100);
// Remove after 3 seconds
setTimeout(() => {
notification.classList.add('translate-y-full', 'opacity-0');
setTimeout(() => notification.remove(), 300);
}, 3000);
}
// Simulate real-time data updates
function updateMetrics() {
// Update heart rate with slight variation
const heartRateElements = document.querySelectorAll('[data-metric="heart-rate"]');
heartRateElements.forEach(el => {
const currentRate = parseInt(el.textContent);
const variation = Math.floor(Math.random() * 3) - 1;
const newRate = Math.max(50, Math.min(60, currentRate + variation));
el.textContent = newRate + ' bpm';
});
}
// Initialize app on load
window.addEventListener('load', function() {
showView('dashboardView');
// Update metrics every 30 seconds
setInterval(updateMetrics, 30000);
});
// Handle page visibility changes
document.addEventListener('visibilitychange', function() {
if (!document.hidden) {
// Refresh data when page becomes visible again
if (currentView === 'strainView') {
initializeStrainCharts();
} else if (currentView === 'sleepView') {
initializeSleepCharts();
} else if (currentView === 'recoveryView') {
initializeRecoveryChart();
}
}
});
// Touch gestures for mobile
let touchStartX = 0;
let touchEndX = 0;
document.addEventListener('touchstart', function(e) {
touchStartX = e.changedTouches[0].screenX;
});
document.addEventListener('touchend', function(e) {
touchEndX = e.changedTouches[0].screenX;
handleSwipe();
});
function handleSwipe() {
const swipeThreshold = 50;
const diff = touchStartX - touchEndX;
if (Math.abs(diff) > swipeThreshold) {
const views = ['dashboardView', 'strainView', 'sleepView', 'recoveryView', 'journalView'];
const currentIndex = views.indexOf(currentView);
if (diff > 0 && currentIndex < views.length - 1) {
// Swipe left - next view
showView(views[currentIndex + 1]);
} else if (diff < 0 && currentIndex > 0) {
// Swipe right - previous view
showView(views[currentIndex - 1]);
}
}
}