154 lines
6.7 KiB
JavaScript
154 lines
6.7 KiB
JavaScript
export class FitnessDashboard {
|
|
constructor(containerId) {
|
|
this.container = document.getElementById(containerId);
|
|
// Mock Data
|
|
this.data = {
|
|
steps: { current: 8432, goal: 10000 },
|
|
sleep: { current: 6.5, goal: 8 },
|
|
history: {
|
|
steps: [4500, 7200, 10500, 8900, 6000, 11200, 8432],
|
|
sleep: [5.5, 6.0, 7.5, 8.2, 5.0, 9.0, 6.5]
|
|
}
|
|
};
|
|
|
|
this.render();
|
|
// Delay animation to allow DOM paint
|
|
setTimeout(() => this.animate(), 100);
|
|
}
|
|
|
|
render() {
|
|
const stepPercent = Math.min((this.data.steps.current / this.data.steps.goal) * 100, 100);
|
|
const sleepPercent = Math.min((this.data.sleep.current / this.data.sleep.goal) * 100, 100);
|
|
|
|
// Ring Config
|
|
const center = 100;
|
|
const radiusOuter = 80;
|
|
const radiusInner = 55;
|
|
|
|
const circumOuter = 2 * Math.PI * radiusOuter;
|
|
const circumInner = 2 * Math.PI * radiusInner;
|
|
|
|
this.container.innerHTML = `
|
|
<div class="fitness-container">
|
|
<h2 class="section-title">DAILY ACTIVITY</h2>
|
|
|
|
<!-- Top: Concentric Rings -->
|
|
<div class="rings-wrapper">
|
|
<svg class="concentric-svg" viewBox="0 0 200 200">
|
|
<!-- Outer Track (Steps) -->
|
|
<circle class="ring-bg" cx="${center}" cy="${center}" r="${radiusOuter}" stroke-width="18"></circle>
|
|
<!-- Inner Track (Sleep) -->
|
|
<circle class="ring-bg" cx="${center}" cy="${center}" r="${radiusInner}" stroke-width="18"></circle>
|
|
|
|
<!-- Outer Progress (Steps - Cyan) -->
|
|
<circle class="ring-progress cyan" cx="${center}" cy="${center}" r="${radiusOuter}"
|
|
stroke-width="18"
|
|
stroke-dasharray="${circumOuter}"
|
|
stroke-dashoffset="${circumOuter}"
|
|
data-offset="${circumOuter - (stepPercent / 100) * circumOuter}"></circle>
|
|
|
|
<!-- Inner Progress (Sleep - Purple) -->
|
|
<circle class="ring-progress purple" cx="${center}" cy="${center}" r="${radiusInner}"
|
|
stroke-width="18"
|
|
stroke-dasharray="${circumInner}"
|
|
stroke-dashoffset="${circumInner}"
|
|
data-offset="${circumInner - (sleepPercent / 100) * circumInner}"></circle>
|
|
|
|
<!-- Icons/Center -->
|
|
<image href="https://fonts.gstatic.com/s/i/materialicons/bolt/v5/24px.svg" x="90" y="90" height="20" width="20" style="filter: invert(1); opacity: 0.5;" />
|
|
</svg>
|
|
|
|
<!-- Legend to explain the rings -->
|
|
<div class="rings-legend">
|
|
<div class="legend-item">
|
|
<span class="dot cyan"></span> STEPS
|
|
</div>
|
|
<div class="legend-item">
|
|
<span class="dot purple"></span> SLEEP
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Middle: Device -->
|
|
<div class="device-card">
|
|
<div class="device-info">
|
|
<span class="device-name">TicWatch Pro 5</span>
|
|
<span class="device-status">Disconnected</span>
|
|
</div>
|
|
<button id="connect-watch-btn" class="connect-glow-btn">LINK</button>
|
|
</div>
|
|
|
|
<!-- Box 1: Steps (Current + History) -->
|
|
<div class="stat-card">
|
|
<div class="stat-header">
|
|
<div>
|
|
<span class="stat-label">STEPS</span>
|
|
<div class="stat-value">${this.data.steps.current}</div>
|
|
<div class="stat-sub">Goal: ${this.data.steps.goal}</div>
|
|
</div>
|
|
<div class="icon-box cyan-box">
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-divider"></div>
|
|
|
|
<div class="chart-container small">
|
|
${this.generateBars(this.data.history.steps, 12000, 'cyan')}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Box 2: Sleep (Current + History) -->
|
|
<div class="stat-card">
|
|
<div class="stat-header">
|
|
<div>
|
|
<span class="stat-label">SLEEP</span>
|
|
<div class="stat-value">${this.data.sleep.current}h</div>
|
|
<div class="stat-sub">Goal: ${this.data.sleep.goal}h</div>
|
|
</div>
|
|
<div class="icon-box purple-box">
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-divider"></div>
|
|
|
|
<div class="chart-container small">
|
|
${this.generateBars(this.data.history.sleep, 10, 'purple')}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
this.attachEvents();
|
|
}
|
|
|
|
generateBars(data, max, colorClass) {
|
|
const days = ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
|
|
return data.map((val, index) => {
|
|
const height = Math.min((val / max) * 100, 100);
|
|
return `
|
|
<div class="chart-column">
|
|
<div class="chart-bar ${colorClass}" style="height: ${height}%"></div>
|
|
<span class="chart-day">${days[index]}</span>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
animate() {
|
|
this.container.querySelectorAll('.ring-progress').forEach(ring => {
|
|
ring.style.strokeDashoffset = ring.dataset.offset;
|
|
});
|
|
}
|
|
|
|
attachEvents() {
|
|
const btn = this.container.querySelector('#connect-watch-btn');
|
|
if (btn) {
|
|
btn.addEventListener('click', () => {
|
|
alert("Placeholder: This feature would connect to the WearOS API in the native app.");
|
|
});
|
|
}
|
|
}
|
|
}
|