/** * GoingsOn - Finish & Review Module * Modal-based end-of-period reflection ritual for Day, Week, and Month views. * Also owns nudge-dot logic on the Finish & Review button. */ (function() { 'use strict'; // ============ Settings ============ function getSettings() { return { workStartHour: parseInt(localStorage.getItem('goingson-work-start-hour') || '9', 10), workEndHour: parseInt(localStorage.getItem('goingson-work-end-hour') || '17', 10), planNudges: localStorage.getItem('goingson-plan-nudges') !== 'disabled', reviewNudges: localStorage.getItem('goingson-review-nudges') !== 'disabled', }; } // ============ Nudge Dot ============ /** * Show or hide a nudge dot on the Finish & Review button for a view. * @param {string} viewName - 'day', 'week', or 'month' * @param {boolean} show - Whether to show the dot */ function updateDot(viewName, show) { const btn = document.getElementById(`${viewName}-finish-review-btn`); if (!btn) return; let dot = btn.querySelector('.toggle-nudge-dot'); if (show && !dot) { dot = document.createElement('span'); dot.className = 'toggle-nudge-dot'; btn.appendChild(dot); } else if (!show && dot) { dot.remove(); } } // ============ Work Hours ============ function isDuringWorkHours() { const s = getSettings(); const hour = new Date().getHours(); return hour >= s.workStartHour && hour < s.workEndHour; } function isAfterWorkHours() { const s = getSettings(); return new Date().getHours() >= s.workEndHour; } // ============ Reflection ============ /** * Render the reflection prompts card used by Day, Week, and Month modals. * @param {Object} opts * @param {string} opts.idPrefix - Prefix for textarea ids (e.g. "daily") * @param {Array<{key, label, placeholder, value}>} opts.prompts * @returns {string} HTML string */ function renderReflection({ idPrefix, prompts }) { const esc = GoingsOn.utils.escapeHtml; const escAttr = GoingsOn.utils.escapeAttr; const fields = prompts.map(p => `
`).join(''); return `

Reflection

${fields}
`; } /** * Wire input listeners to the reflection textareas. Calls `onChange` with a * map of {key: value} whenever the user types (debounced). */ function wireReflectionAutosave({ idPrefix, prompts, onChange, debounceMs = 500 }) { const fire = GoingsOn.utils.debounce(() => { const values = {}; for (const p of prompts) { const el = document.getElementById(`${idPrefix}-${p.key}`); values[p.key] = el ? el.value : ''; } onChange(values); }, debounceMs); for (const p of prompts) { const el = document.getElementById(`${idPrefix}-${p.key}`); if (el) el.addEventListener('input', fire); } } /** * Auto-grow reflection textareas on touch devices. */ function autoGrowReflection({ idPrefix, prompts }) { if (!GoingsOn.touch?.isTouchDevice) return; for (const p of prompts) { const el = document.getElementById(`${idPrefix}-${p.key}`); if (el) GoingsOn.utils.autoGrow(el); } } // ============ Status Badge ============ /** * Set the review-status badge for a view's header. * @param {string} elementId - id of the element * @param {string} periodState - 'current' | 'past' | 'future' * @param {boolean} isReviewed - whether the period has been reviewed */ function setStatusBadge(elementId, periodState, isReviewed) { const el = document.getElementById(elementId); if (!el) return; el.classList.remove('completed', 'pending', 'unreviewed'); if (periodState === 'future') { el.classList.add('hidden'); el.textContent = ''; return; } el.classList.remove('hidden'); if (isReviewed) { el.classList.add('completed'); el.textContent = 'Review Complete'; } else if (periodState === 'current') { el.classList.add('pending'); el.textContent = 'Review Pending'; } else { el.classList.add('unreviewed'); el.textContent = 'Not Reviewed'; } } // ============ Namespace ============ GoingsOn.planReviewToggle = { updateDot, getSettings, isDuringWorkHours, isAfterWorkHours, renderReflection, wireReflectionAutosave, autoGrowReflection, setStatusBadge, }; })();