/** * GoingsOn - Monthly Review Render Module * Pure rendering functions for the Month view. */ (function() { 'use strict'; const esc = GoingsOn.utils.escapeHtml; const escAttr = GoingsOn.utils.escapeAttr; /** * Truncate a string with an ellipsis. * @param {string} str - String to truncate * @param {number} len - Maximum length * @returns {string} Truncated string */ function truncate(str, len) { if (!str) return ''; return str.length > len ? str.substring(0, len) + '...' : str; } // ============ Heat Map Calendar ============ /** * Render the month heat-map calendar grid. * @param {Object} r - Monthly review data with days, firstDayOffset * @returns {string} HTML string */ function renderHeatMap(r) { const dayHeaders = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; let html = '
'; html += '
'; for (const d of dayHeaders) { html += `
${d}
`; } html += '
'; html += '
'; // Empty cells before the 1st for (let i = 0; i < r.firstDayOffset; i++) { html += '
'; } for (const day of r.days) { const classes = ['month-heatmap-cell']; if (day.isToday) classes.push('today'); if (day.isPast) classes.push('past'); if (day.isVacation) classes.push('vacation'); classes.push(`intensity-${day.intensity}`); html += `
`; html += `${day.dayNumber}`; if (day.completedCount > 0 || day.eventCount > 0) { html += '
'; if (day.completedCount > 0) html += `${day.completedCount}`; if (day.eventCount > 0) html += `${day.eventCount}`; html += '
'; } html += '
'; } // Empty cells after the last day const totalCells = r.firstDayOffset + r.days.length; const remainder = totalCells % 7; if (remainder > 0) { for (let i = 0; i < 7 - remainder; i++) { html += '
'; } } html += '
'; return html; } // ============ Accomplished ============ /** * Render the Accomplished card with a sample of completed tasks for the month. * @param {Object} r - Monthly review data * @returns {string} HTML string */ function renderAccomplished(r) { const count = r.tasksCompletedCount || 0; const top = r.tasksCompletedTop || []; if (count === 0) return ''; const items = top.map(t => `
  • ${esc(t.description)} ${t.projectName ? `${esc(t.projectName)}` : ''}
  • `).join(''); return `
    Accomplished ${count} completed
    ${count > top.length ? `

    … and ${count - top.length} more

    ` : ''}
    `; } // ============ Month in Numbers ============ /** * Render the "Month in Numbers" stats card. * @param {Object} r - Monthly review data * @returns {string} HTML string */ function renderStats(r) { let html = '
    '; html += '

    Month in Numbers

    '; html += '
    '; html += renderStatItem('Tasks Completed', r.tasksCompletedCount, 'completed'); html += renderStatItem('Tasks Created', r.tasksCreatedCount, 'created'); html += renderStatItem('Events', r.eventsCount, 'events'); html += renderStatItem('Best Streak', r.completionStreak + 'd', 'streak'); html += '
    '; if (r.busiestDay || r.quietestDay) { html += '
    '; if (r.busiestDay) html += `Busiest: ${formatDateShort(r.busiestDay)}`; if (r.quietestDay) html += `Quietest: ${formatDateShort(r.quietestDay)}`; html += '
    '; } html += '
    '; return html; } function renderStatItem(label, value, type) { return `
    ${value}${label}
    `; } function formatDateShort(dateStr) { if (!dateStr) return ''; const d = new Date(dateStr + 'T12:00:00'); return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }); } // ============ Project Health ============ /** * Render the Project Health card showing per-project activity direction. * @param {Object} r - Monthly review data with projectPulse array * @returns {string} HTML string */ function renderProjectPulse(r) { if (!r.projectPulse || r.projectPulse.length === 0) return ''; let html = '
    '; html += '

    Project Health

    '; html += '
    '; for (const p of r.projectPulse) { const dirClass = p.direction === 'shrinking' ? 'positive' : p.direction === 'growing' ? 'negative' : 'neutral'; const arrow = p.direction === 'shrinking' ? '↓' : p.direction === 'growing' ? '↑' : '↔'; html += `
    `; html += `${esc(truncate(p.name, 24))}`; html += `+${p.completed} done / +${p.created} new`; html += `${arrow}`; html += '
    '; } html += '
    '; return html; } // ============ Monthly Goals ============ /** * Render the Monthly Goals card with 3 goal slots. * @param {Object} r - Monthly review data with goals array * @returns {string} HTML string */ function renderGoals(r) { let html = '
    '; html += '
    '; html += 'Monthly Goals'; html += 'Set up to 3 goals'; html += '
    '; html += '
    '; for (let pos = 1; pos <= 3; pos++) { const goal = r.goals.find(g => g.position === pos); if (goal) { html += renderGoalItem(goal, pos); } else { html += renderEmptyGoalSlot(r.month, pos); } } html += '
    '; return html; } function renderGoalItem(goal, position) { const statusIcons = { active: '○', done: '✓', abandoned: '✗' }; const icon = statusIcons[goal.status] || '○'; let html = `
    `; html += `Goal #${position}`; html += `
    `; html += ``; html += `${esc(goal.text)}`; html += ``; html += `
    `; html += '
    '; return html; } function renderEmptyGoalSlot(month, position) { return `
    Goal #${position} + Add goal
    `; } // ============ Reflection ============ /** * Render the reflection card using the shared helper. * @param {Object} r - Monthly review data with reflection object * @returns {string} HTML string */ function renderReflection(r) { return GoingsOn.planReviewToggle.renderReflection({ idPrefix: 'monthly', prompts: [ { key: 'highlight', label: 'What was the highlight of this month?', placeholder: "Something you're proud of...", value: r.reflection?.highlightText || '' }, { key: 'change', label: 'What would you change?', placeholder: 'Something to improve next month...', value: r.reflection?.changeText || '' }, ], }); } // ============ Patterns ============ /** * Render the Patterns card with observed behavioral patterns. * @param {Object} r - Monthly review data with patterns array * @returns {string} HTML string */ function renderPatterns(r) { if (!r.patterns || r.patterns.length === 0) return ''; let html = '
    '; html += '

    Patterns

    '; html += '
    '; return html; } // ============ Exports ============ GoingsOn.monthlyReviewRender = { renderHeatMap, renderAccomplished, renderStats, renderProjectPulse, renderGoals, renderReflection, renderPatterns, }; })();