From 030bf30b1407ec923a29467c07296dc4f43ab92e Mon Sep 17 00:00:00 2001 From: "Konrad M." Date: Tue, 21 Apr 2026 21:52:43 +0200 Subject: [PATCH] fix(tasks): show due time on task cards; use current moment for overdue state formatDueDate now accepts due_time; displays it on today/tomorrow entries. Overdue state is computed against the current moment, not midnight, so a task due at 09:00 is correctly marked overdue once that time has passed. --- public/pages/tasks.js | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/public/pages/tasks.js b/public/pages/tasks.js index 3708b9b..47eb400 100644 --- a/public/pages/tasks.js +++ b/public/pages/tasks.js @@ -8,7 +8,7 @@ import { api } from '/api.js'; import { renderRRuleFields, bindRRuleEvents, getRRuleValues } from '/rrule-ui.js'; import { openModal as openSharedModal, closeModal, wireBlurValidation, validateAll, btnSuccess, btnError, promptModal, confirmModal } from '/components/modal.js'; import { stagger, vibrate } from '/utils/ux.js'; -import { t, formatDate } from '/i18n.js'; +import { t, formatDate, formatTime } from '/i18n.js'; import { esc } from '/utils/html.js'; import { refresh as refreshReminders } from '/reminders.js'; @@ -57,17 +57,29 @@ function initials(name = '') { return name.split(' ').map((w) => w[0]).join('').slice(0, 2).toUpperCase(); } -function formatDueDate(dateStr) { +function formatDueDate(dateStr, timeStr) { if (!dateStr) return null; - const due = new Date(dateStr); - const now = new Date(); - now.setHours(0, 0, 0, 0); - const diffDays = Math.round((due - now) / 86400000); + const dueDate = timeStr ? new Date(`${dateStr}T${timeStr}`) : new Date(`${dateStr}T23:59:59`); + if (isNaN(dueDate)) return null; - if (diffDays < 0) return { label: t('tasks.overdueDay', { count: Math.abs(diffDays) }), cls: 'due-date--overdue' }; - if (diffDays === 0) return { label: t('tasks.dueToday'), cls: 'due-date--today' }; - if (diffDays === 1) return { label: t('tasks.dueTomorrow'), cls: '' }; - return { label: formatDate(due), cls: '' }; + const now = new Date(); + const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + const dueDay = new Date(dueDate.getFullYear(), dueDate.getMonth(), dueDate.getDate()); + const calDayDiff = Math.round((dueDay - today) / (1000 * 60 * 60 * 24)); + + const timeLabel = timeStr ? ` – ${formatTime(dueDate)}` : ''; + const fullLabel = timeStr ? `${formatDate(dueDate)}, ${formatTime(dueDate)}` : formatDate(dueDate); + + if (dueDate < now) { + return { label: `${t('tasks.overdue')} – ${fullLabel}`, cls: 'due-date--overdue' }; + } + if (calDayDiff === 0) { + return { label: `${t('tasks.dueToday')}${timeLabel}`, cls: 'due-date--today' }; + } + if (calDayDiff === 1) { + return { label: `${t('tasks.dueTomorrow')}${timeLabel}`, cls: '' }; + } + return { label: fullLabel, cls: '' }; } function groupBy(tasks, mode) { @@ -119,8 +131,8 @@ function renderPriorityBadge(priority) { `; } -function renderDueDate(dateStr) { - const d = formatDueDate(dateStr); +function renderDueDate(dateStr, timeStr) { + const d = formatDueDate(dateStr, timeStr); if (!d) return ''; return ` ${d.label} @@ -178,7 +190,7 @@ function renderTaskCard(task, opts = {}) {
${renderPriorityBadge(task.priority)} - ${renderDueDate(task.due_date)} + ${renderDueDate(task.due_date, task.due_time)} ${task.is_recurring ? `` : ''} ${task.category !== 'misc' ? `${CATEGORY_LABELS()[task.category] ?? task.category}` : ''}
@@ -584,7 +596,7 @@ function kanbanNextStatus(status) { } function renderKanbanCard(task) { - const due = formatDueDate(task.due_date); + const due = formatDueDate(task.due_date, task.due_time); const next = kanbanNextStatus(task.status); const icon = next === 'done' ? 'check' : next === 'in_progress' ? 'circle-play' : 'rotate-ccw'; const nextLabel = next === 'done'