/**
* 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 `
`;
}
/**
* 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,
};
})();