feat(board): add live task countdowns
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user