|
|
@@ -0,0 +1,98 @@
|
|
|
+const Stats = (() => {
|
|
|
+ async function init() {
|
|
|
+ await loadStats();
|
|
|
+ }
|
|
|
+
|
|
|
+ async function loadStats() {
|
|
|
+ const content = document.getElementById('stats-content');
|
|
|
+ content.innerHTML = '<div class="loading">Loading stats...</div>';
|
|
|
+
|
|
|
+ try {
|
|
|
+ const stats = await API.getStats();
|
|
|
+ renderStats(stats);
|
|
|
+ } catch (e) {
|
|
|
+ content.innerHTML = '<div class="error-msg">Error loading stats: ' + escHtml(e.message) + '</div>';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderStats(stats) {
|
|
|
+ const content = document.getElementById('stats-content');
|
|
|
+ let html = '';
|
|
|
+
|
|
|
+ // Summary totals
|
|
|
+ if (stats.total !== undefined || stats.totalDistance !== undefined) {
|
|
|
+ const totalDist = stats.total?.distance || stats.totalDistance || 0;
|
|
|
+ const totalTracks = stats.total?.count || stats.trackCount || 0;
|
|
|
+ html += `<div class="stats-section">
|
|
|
+ <h3>Total</h3>
|
|
|
+ <div class="stat-row">
|
|
|
+ <span class="stat-label">Tracks</span>
|
|
|
+ <div class="stat-bar-wrap"><div class="stat-bar" style="width:100%"></div></div>
|
|
|
+ <span class="stat-value">${totalTracks}</span>
|
|
|
+ </div>
|
|
|
+ <div class="stat-row">
|
|
|
+ <span class="stat-label">Distance</span>
|
|
|
+ <div class="stat-bar-wrap"><div class="stat-bar" style="width:100%"></div></div>
|
|
|
+ <span class="stat-value">${formatDistance(totalDist)}</span>
|
|
|
+ </div>
|
|
|
+ </div>`;
|
|
|
+ }
|
|
|
+
|
|
|
+ // By year
|
|
|
+ if (stats.byYear && stats.byYear.length > 0) {
|
|
|
+ const maxDist = Math.max(...stats.byYear.map(s => s.distance || 0));
|
|
|
+ html += '<div class="stats-section"><h3>By Year</h3>';
|
|
|
+ for (const s of stats.byYear) {
|
|
|
+ const pct = maxDist > 0 ? ((s.distance || 0) / maxDist * 100) : 0;
|
|
|
+ html += `<div class="stat-row">
|
|
|
+ <span class="stat-label">${s.year}</span>
|
|
|
+ <div class="stat-bar-wrap"><div class="stat-bar" style="width:${pct.toFixed(1)}%"></div></div>
|
|
|
+ <span class="stat-value">${formatDistance(s.distance)}</span>
|
|
|
+ </div>`;
|
|
|
+ }
|
|
|
+ html += '</div>';
|
|
|
+ }
|
|
|
+
|
|
|
+ // By month (last 24 months)
|
|
|
+ if (stats.byMonth && stats.byMonth.length > 0) {
|
|
|
+ const recent = stats.byMonth.slice(-24);
|
|
|
+ const maxDist = Math.max(...recent.map(s => s.distance || 0));
|
|
|
+ html += '<div class="stats-section"><h3>By Month (last 24)</h3>';
|
|
|
+ for (const s of recent) {
|
|
|
+ const pct = maxDist > 0 ? ((s.distance || 0) / maxDist * 100) : 0;
|
|
|
+ const label = `${s.year}-${String(s.month).padStart(2, '0')}`;
|
|
|
+ html += `<div class="stat-row">
|
|
|
+ <span class="stat-label">${label}</span>
|
|
|
+ <div class="stat-bar-wrap"><div class="stat-bar" style="width:${pct.toFixed(1)}%"></div></div>
|
|
|
+ <span class="stat-value">${formatDistance(s.distance)}</span>
|
|
|
+ </div>`;
|
|
|
+ }
|
|
|
+ html += '</div>';
|
|
|
+ }
|
|
|
+
|
|
|
+ // By week (last 52 weeks)
|
|
|
+ if (stats.byWeek && stats.byWeek.length > 0) {
|
|
|
+ const recent = stats.byWeek.slice(-52);
|
|
|
+ const maxDist = Math.max(...recent.map(s => s.distance || 0));
|
|
|
+ html += '<div class="stats-section"><h3>By Week (last 52)</h3>';
|
|
|
+ for (const s of recent) {
|
|
|
+ const pct = maxDist > 0 ? ((s.distance || 0) / maxDist * 100) : 0;
|
|
|
+ const label = `${s.year}-W${String(s.week).padStart(2, '0')}`;
|
|
|
+ html += `<div class="stat-row">
|
|
|
+ <span class="stat-label">${label}</span>
|
|
|
+ <div class="stat-bar-wrap"><div class="stat-bar" style="width:${pct.toFixed(1)}%"></div></div>
|
|
|
+ <span class="stat-value">${formatDistance(s.distance)}</span>
|
|
|
+ </div>`;
|
|
|
+ }
|
|
|
+ html += '</div>';
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!html) {
|
|
|
+ html = '<div class="empty-list">No tracks yet. Upload some GPX files to see stats.</div>';
|
|
|
+ }
|
|
|
+
|
|
|
+ content.innerHTML = html;
|
|
|
+ }
|
|
|
+
|
|
|
+ return { init, loadStats };
|
|
|
+})();
|