/** * GoingsOn - Weekly Review Render Module * Card rendering for the weekly review grid sections */ (function() { 'use strict'; const esc = GoingsOn.utils.escapeHtml; const escAttr = GoingsOn.utils.escapeAttr; // ============ Helpers ============ /** * Truncate a string with an ellipsis character. * @param {string} str - String to truncate * @param {number} len - Maximum length * @returns {string} Truncated string */ function truncate(str, len) { if (str.length <= len) return str; return str.substring(0, len - 1) + '\u2026'; } // ============ Section Renderers ============ /** * Render the week-at-a-glance timeline with day dots and event summaries. * @param {Object} r - Weekly review data object * @returns {string} HTML string */ function renderWeekTimeline(r) { const days = r.timelineDays || []; return `
Week at a Glance ${getTodayLabel(days)}
${days.map(day => `
${esc(day.dayName)}
${day.dayNumber}
${renderDayDots(day)}
${(day.events && day.events.length > 0) ? `
${day.events.slice(0, 3).map(e => `
${esc(e.formattedTime.split(' ').pop())} ${esc(truncate(e.title, 12))}
`).join('')} ${day.events.length > 3 ? `
+${day.events.length - 3}
` : ''}
` : ''}
`).join('')}
`; } function getTodayLabel(days) { const today = days.find(d => d.isToday); if (today) { return `Today: ${today.dayName}`; } return ''; } function renderDayDots(day) { if (day.isVacation) { return ''; } const dots = []; // Completed tasks (green) for (let i = 0; i < Math.min(day.completedCount, 3); i++) { dots.push(''); } // Events (purple) for (let i = 0; i < Math.min(day.eventCount, 2); i++) { dots.push(''); } // Overdue (red) for (let i = 0; i < Math.min(day.overdueCount, 2); i++) { dots.push(''); } // Due (blue) - for future days if (!day.isPast && day.dueCount > 0) { for (let i = 0; i < Math.min(day.dueCount, 2); i++) { dots.push(''); } } return dots.join(''); } function renderTimelineEvents(r) { const days = r.timelineDays || []; // Collect all events from timeline days, grouped by day const daysWithEvents = days.filter(d => d.events && d.events.length > 0); if (daysWithEvents.length === 0) return ''; return `
Week's Events
${daysWithEvents.map(day => `
${esc(day.dayName)} ${day.dayNumber}
${day.events.map(e => renderEventItemCompact(e)).join('')}
`).join('')}
`; } /** * Render the Accomplished card with completed task count and list. * @param {Object} r - Weekly review data * @returns {string} HTML string */ function renderAccomplished(r) { const count = r.tasksCompletedCount || 0; const eventCount = r.eventsOccurredCount || 0; return `
Accomplished ${count} completed
${count > 0 ? `
You completed ${count} task${count !== 1 ? 's' : ''} ${eventCount > 0 ? ` and attended ${eventCount} event${eventCount !== 1 ? 's' : ''}` : ''}.
` : ''}
`; } /** * Render the Needs Attention card with overdue and carried-over tasks. * @param {Object} r - Weekly review data * @returns {string} HTML string */ function renderNeedsAttention(r) { const overdueCount = r.tasksOverdueCount || 0; const carriedOverCount = r.carriedOverCount || 0; return `
Needs Attention ${overdueCount > 0 ? ` ${overdueCount} overdue ` : ''}
${overdueCount}
Overdue
${carriedOverCount}
Carried Over
`; } /** * Render the Due This Week card with tasks due in the next 7 days. * @param {Object} r - Weekly review data * @returns {string} HTML string */ function renderDueThisWeek(r) { const count = r.tasksDueNextWeekCount || 0; return `
Due This Week ${count} task${count !== 1 ? 's' : ''}
${count === 0 ? '

No tasks due this week

' : ''}
`; } /** * Render the Focus section with 3 priority slots and suggested tasks. * @param {Object} r - Weekly review data * @returns {string} HTML string */ function renderFocusSection(r) { const focused = r.focusedTasks || []; const available = r.availableForFocus || []; const slots = []; for (let i = 0; i < 3; i++) { const task = focused[i]; if (task) { const isPrimary = i === 0; slots.push(`
Priority #${i + 1} ${esc(task.description)} ${task.projectName ? esc(task.projectName) : 'No project'} ${task.dueFormatted ? ' · Due ' + task.dueFormatted : ''}
`); } else { slots.push(`
Priority #${i + 1} Press Enter or click a task to add focus
`); } } return `
This Week's Focus Pick up to 3 priorities
${slots.join('')}
${available.length > 0 && focused.length < 3 ? `

Suggested tasks to focus on:

${available.slice(0, 5).map(t => ` `).join('')}
` : ''} ${focused.length > 0 ? `
` : ''}
`; } /** * Render the Projects Health card with per-project status. * @param {Object} r - Weekly review data * @returns {string} HTML string */ function renderProjectsHealth(r) { const projects = r.projectHealth || []; if (projects.length === 0) { return ''; } return `
Projects Health
${projects.slice(0, 6).map(p => `
${esc(p.name)}
${p.overdueCount > 0 ? `${p.overdueCount} overdue · ` : ''} ${p.activeCount} active · ${p.totalCount} total
`).join('')}
`; } /** * Render the reflection section using the shared helper. * Draft handling lives in weekly-review.js; this just resolves displayed values. * @param {Object} r - Weekly review data * @param {Function} getDraft - Returns saved draft from localStorage * @returns {string} HTML string */ function renderReflection(r, getDraft) { const notes = r.notes || ''; let wentWell = ''; let improve = ''; const wentWellMatch = notes.match(/What went well:\s*([\s\S]*?)(?:What could be improved:|$)/i); const improveMatch = notes.match(/What could be improved:\s*([\s\S]*?)$/i); if (wentWellMatch) wentWell = wentWellMatch[1].trim(); if (improveMatch) improve = improveMatch[1].trim(); if (!wentWellMatch && !improveMatch && notes) wentWell = notes; const draft = getDraft(); if (draft.weekStart === r.weekStart || (!r.isCompleted && draft.savedAt)) { if (draft.wentWell !== undefined) wentWell = draft.wentWell; if (draft.improve !== undefined) improve = draft.improve; } return GoingsOn.planReviewToggle.renderReflection({ idPrefix: 'weekly', prompts: [ { key: 'went-well', label: 'What went well?', placeholder: 'Completed the budget ahead of schedule...', value: wentWell }, { key: 'improve', label: 'What could be improved?', placeholder: 'Need to block more focus time...', value: improve }, ], }); } /** * Render the vacation day toggle buttons (Mon-Sun). * @param {Object} r - Weekly review data with vacationDays array * @returns {string} HTML string */ function renderVacationToggles(r) { const dayLabels = ['M', 'T', 'W', 'T', 'F', 'S', 'S']; const vacationDays = r.vacationDays || []; return `
Days Off
${dayLabels.map((label, i) => ` `).join('')}
`; } // ============ Task/Event Item Renderers ============ /** * Render a completed task list item with checkmark. * @param {Object} task - Task object * @returns {string} HTML string */ function renderCompletedTaskItem(task) { return `
  • ${esc(task.description)} ${task.projectName ? `${esc(task.projectName)}` : ''}
  • `; } /** * Render a compact task list item with optional overdue indicator. * @param {Object} task - Task object * @param {boolean} showOverdue - true to show overdue badge * @returns {string} HTML string */ function renderTaskItemCompact(task, showOverdue) { const dueText = task.dueFormatted || ''; return `
  • ${esc(task.description)} ${showOverdue && task.isOverdue ? `${dueText}` : (task.projectName ? `${esc(task.projectName)}` : '') } ${!showOverdue && dueText ? `${dueText}` : ''}
  • `; } /** * Render a compact event item with time and title. * @param {Object} event - Event object with formattedTime, title, projectName * @returns {string} HTML string */ function renderEventItemCompact(event) { return `
    ${esc(event.formattedTime)} ${esc(event.title)} ${event.projectName ? `${esc(event.projectName)}` : ''}
    `; } // ============ Populate Namespace ============ GoingsOn.weeklyReviewRender = { renderWeekTimeline, renderTimelineEvents, renderAccomplished, renderNeedsAttention, renderDueThisWeek, renderFocusSection, renderProjectsHealth, renderReflection, renderVacationToggles, renderCompletedTaskItem, renderTaskItemCompact, renderEventItemCompact, truncate, }; })();