diff --git a/app/web/static/app.js b/app/web/static/app.js index 62b24cc..0ee0719 100644 --- a/app/web/static/app.js +++ b/app/web/static/app.js @@ -1,10 +1,39 @@ -document.addEventListener("DOMContentLoaded", () => { - document.querySelectorAll("time[data-utc]").forEach((el) => { - const date = new Date(el.dataset.utc); - if (Number.isNaN(date.getTime())) { - return; +function formatCountdown(remainingMs) { + if (remainingMs <= 0) { + return "due now"; + } + const totalSeconds = Math.floor(remainingMs / 1000); + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = totalSeconds % 60; + const pad = (n) => String(n).padStart(2, "0"); + if (hours > 0) { + return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`; + } + return `${pad(minutes)}:${pad(seconds)}`; +} + +function startSyncCountdown(el) { + const target = new Date(el.dataset.utc); + if (Number.isNaN(target.getTime())) { + return; + } + el.title = `${target.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" })} ยท ${el.dataset.utc} UTC`; + + let intervalId = null; + const tick = () => { + const remaining = target.getTime() - Date.now(); + el.textContent = formatCountdown(remaining); + if (remaining <= 0 && intervalId !== null) { + clearInterval(intervalId); } - el.textContent = date.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" }); - el.title = `${el.dataset.utc} (UTC)`; - }); + }; + tick(); + if (target.getTime() - Date.now() > 0) { + intervalId = setInterval(tick, 1000); + } +} + +document.addEventListener("DOMContentLoaded", () => { + document.querySelectorAll("time.next-sync[data-utc]").forEach(startSyncCountdown); });