Skip to main content

max / goingson

3.2 KB · 98 lines History Blame Raw
1 /**
2 * @fileoverview Time tracking summary panel for the Day view.
3 *
4 * Collapsible section showing:
5 * - Today's total tracked time
6 * - This week's per-project breakdown (CSS bar chart, no library)
7 */
8 (function() {
9 'use strict';
10
11 const esc = (s) => GoingsOn.utils.escapeHtml(s);
12
13 /**
14 * Format a minute count as a human-readable duration.
15 * @param {number} mins - Minutes to format
16 * @returns {string} Formatted string (e.g., "2h 15m", "45m", "3h")
17 */
18 function formatMinutes(mins) {
19 if (mins < 60) return `${mins}m`;
20 const h = Math.floor(mins / 60);
21 const m = mins % 60;
22 return m > 0 ? `${h}h ${m}m` : `${h}h`;
23 }
24
25 // ============ Render ============
26
27 /**
28 * Render the time tracking summary panel (today's total + weekly per-project bars).
29 * @param {HTMLElement} container - DOM element to render into
30 */
31 async function render(container) {
32 if (!container) return;
33
34 // Rust computes the week window, aggregates by project, sorts, and rolls up
35 // the percentages (see get_time_summary_panel). The JS only renders.
36 let panel;
37 try {
38 panel = await GoingsOn.api.timeTracking.getSummaryPanel();
39 } catch (err) {
40 console.error('Failed to load time summary:', err);
41 return;
42 }
43
44 const projects = panel.projects || [];
45
46 let html = `
47 <div class="time-summary-section">
48 <button class="time-summary-toggle" aria-expanded="true">
49 <span class="time-summary-toggle-icon">&#9660;</span>
50 Time Tracked
51 </button>
52 <div class="time-summary-body">
53 <div class="time-summary-today">
54 <span class="time-summary-today-label">Today</span>
55 <span class="time-summary-today-value">${esc(formatMinutes(panel.todayMinutes))}</span>
56 </div>`;
57
58 if (projects.length > 0) {
59 html += `<div class="time-summary-week-header">This Week</div>`;
60 for (const p of projects) {
61 html += `
62 <div class="time-summary-project">
63 <div class="time-summary-project-info">
64 <span class="time-summary-project-name">${esc(p.name)}</span>
65 <span class="time-summary-project-time">${esc(formatMinutes(p.totalMinutes))}</span>
66 </div>
67 <div class="time-summary-bar">
68 <div class="time-summary-bar-fill" style="width: ${p.barPercent}%"></div>
69 </div>
70 </div>`;
71 }
72 }
73
74 html += `</div></div>`;
75 container.innerHTML = html;
76
77 // Toggle collapse
78 const toggle = container.querySelector('.time-summary-toggle');
79 const body = container.querySelector('.time-summary-body');
80 if (toggle && body) {
81 toggle.addEventListener('click', () => {
82 const expanded = toggle.getAttribute('aria-expanded') === 'true';
83 toggle.setAttribute('aria-expanded', String(!expanded));
84 body.classList.toggle('collapsed', expanded);
85 toggle.querySelector('.time-summary-toggle-icon').textContent = expanded ? '\u25B6' : '\u25BC';
86 });
87 }
88 }
89
90 // ============ Namespace ============
91
92 GoingsOn.timeSummary = {
93 render,
94 formatMinutes,
95 };
96
97 })();
98