/** * GoingsOn - Tasks Render Module * Task row rendering, badges, subtask modal rendering */ (function() { 'use strict'; const esc = GoingsOn.utils.escapeHtml; const escAttr = GoingsOn.utils.escapeAttr; // ============ Task Rendering Helpers ============ /** * Render subtask count and annotation badges for a task row. * @param {Object} task - Task object with subtaskCount, subtaskCompleted, annotations * @returns {string} HTML string of badge elements, or empty string if none */ function renderTaskBadges(task) { let badges = ''; const subtaskCount = task.subtaskCount; const completedSubtasks = task.subtaskCompleted; const annotationCount = task.annotations ? task.annotations.length : 0; if (subtaskCount > 0) { badges += `${completedSubtasks}/${subtaskCount}`; } if (annotationCount > 0) { badges += `Notes: ${annotationCount}`; } return badges ? `${badges}` : ''; } /** * Format a task's recurrence pattern for display. * Only shows recurrence when the task also has a due date. * @param {Object} task - Task object with recurrence and due fields * @returns {string} Recurrence label or '-' */ function formatRecurrence(task) { // Only show recurrence if task has both recurrence AND a due date if (task.recurrence && task.recurrence !== 'None' && task.due) { return task.recurrence; } return '-'; } /** * Render a single task row as a div (for virtual scrolling). * @param {Object} t - Task object * @param {number} index - Task index * @returns {string} HTML string */ /** * Format a minute count as a human-readable duration string. * @param {number} mins - Minutes to format * @returns {string} Formatted string (e.g., "1h 30m" or "45m") */ function formatMinutes(mins) { if (mins >= 60) return `${Math.floor(mins / 60)}h ${mins % 60}m`; return `${mins}m`; } /** * Render time tracking badge for a task (active timer or tracked/estimated). * @param {Object} t - Task object with timerActive, estimatedMinutes, actualMinutes * @returns {string} HTML string for the time badge, or empty string */ function renderTimeBadge(t) { if (t.timerActive) { return ''; } if (t.estimatedMinutes || t.actualMinutes) { const actual = t.actualMinutes || 0; const est = t.estimatedMinutes; const isOver = est && actual > est; const label = est ? `${formatMinutes(actual)} / ${formatMinutes(est)}` : formatMinutes(actual); return `${esc(label)}`; } return ''; } function renderTaskRow(t, index) { const progress = t.subtaskProgress ?? 0; const displayDesc = t.displayDescription || t.description; const isSelected = GoingsOn.tasks.selection.isSelected(t.id); const isStarted = t.status === 'Started'; return `