feat(board): add live task countdowns

This commit is contained in:
aj
2026-08-13 17:01:19 -04:00
parent 94f85731ef
commit ee9bf9f2c1
2 changed files with 52 additions and 2 deletions
+50
View File
@@ -55,6 +55,50 @@
});
}
// ========================================
// Upcoming task countdowns
// ========================================
var countdownTimer = null;
function formatCountdown(totalSeconds, isFuture) {
var remaining = totalSeconds;
var days = Math.floor(remaining / 86400);
remaining %= 86400;
var hours = Math.floor(remaining / 3600);
remaining %= 3600;
var minutes = Math.floor(remaining / 60);
var seconds = remaining % 60;
var parts = [];
if (days) parts.push(days + 'd');
if (hours) parts.push(hours + 'h');
if (minutes) parts.push(minutes + 'm');
if (seconds && !days && !hours) parts.push(seconds + 's');
if (parts.length === 0) parts.push('0s');
return isFuture ? parts.join(' ') + ' remaining' : 'Overdue by ' + parts.join(' ');
}
function updateCountdowns() {
var now = Date.now();
document.querySelectorAll('[data-due-at] .task-countdown, time.task-countdown[data-due-at]').forEach(function(countdown) {
var dueAtElement = countdown.closest('[data-due-at]') || countdown;
var dueAt = Date.parse(dueAtElement.dataset.dueAt);
if (Number.isNaN(dueAt)) return;
var deltaMilliseconds = dueAt - now;
var totalSeconds = Math.floor(Math.abs(deltaMilliseconds) / 1000);
countdown.textContent = formatCountdown(totalSeconds, deltaMilliseconds > 0);
});
}
function initCountdowns() {
if (countdownTimer) clearInterval(countdownTimer);
updateCountdowns();
countdownTimer = setInterval(updateCountdowns, 1000);
}
// ========================================
// Detail Panel helpers
// ========================================
@@ -111,6 +155,7 @@
// Initialize Kanban drag-and-drop on page load
document.addEventListener('DOMContentLoaded', function() {
initKanban();
initCountdowns();
// "New Task" header button — scroll to and focus the create-task input in the Pending column
var newTaskBtn = document.getElementById('new-task-trigger');
@@ -132,6 +177,11 @@
if (target && (target.id === 'kanban-board' || target.id === 'board-content' || (target.closest && target.closest('#kanban-board')))) {
initKanban();
}
if (target && (target.id === 'board-content' ||
(target.matches && target.matches('.upcoming-tasks')) ||
(target.querySelector && target.querySelector('.upcoming-tasks')))) {
initCountdowns();
}
});
// Also handle htmx:afterSettle for cases where the DOM isn't fully ready on afterSwap