Tier 3: timetracking CRUD + time tracking with topbar timer

This commit is contained in:
2026-02-28 04:48:37 +00:00
parent 6ad642084d
commit 7b259b9597
6 changed files with 497 additions and 0 deletions

View File

@@ -168,3 +168,69 @@ function escHtml(s) {
d.textContent = s;
return d.innerHTML;
}
// ---- Timer Pill (topbar running timer) ----
let timerStartAt = null;
let timerInterval = null;
function formatElapsed(seconds) {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
if (h > 0) {
return h + ':' + String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0');
}
return m + ':' + String(s).padStart(2, '0');
}
function updateTimerPill() {
if (!timerStartAt) return;
const now = new Date();
const secs = Math.floor((now - timerStartAt) / 1000);
const el = document.getElementById('timer-pill-elapsed');
if (el) el.textContent = formatElapsed(secs);
}
async function pollTimer() {
try {
const resp = await fetch('/time/running');
const data = await resp.json();
const pill = document.getElementById('timer-pill');
if (!pill) return;
if (data.running) {
pill.classList.remove('hidden');
timerStartAt = new Date(data.start_at);
const taskEl = document.getElementById('timer-pill-task');
if (taskEl) {
taskEl.textContent = data.task_title;
taskEl.href = '/tasks/' + data.task_id;
}
updateTimerPill();
// Start 1s interval if not already running
if (!timerInterval) {
timerInterval = setInterval(updateTimerPill, 1000);
}
} else {
pill.classList.add('hidden');
timerStartAt = null;
if (timerInterval) {
clearInterval(timerInterval);
timerInterval = null;
}
}
} catch (err) {
// Silently ignore polling errors
}
}
// Poll on load, then every 30s
document.addEventListener('DOMContentLoaded', () => {
pollTimer();
setInterval(pollTimer, 30000);
});