/** * GoingsOn - Day Planning Schedule & Review Module * Schedule task modal, daily review. */ (function() { 'use strict'; const esc = GoingsOn.utils.escapeHtml; const escAttr = GoingsOn.utils.escapeAttr; // ============ Schedule Task Modal ============ /** * Open the schedule task modal with time slot picker and duration presets. * @param {string} id - Task ID to schedule */ function openScheduleTaskModal(id) { const now = new Date(); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const timeSlots = []; for (let hour = 6; hour <= 21; hour++) { for (let min = 0; min < 60; min += 15) { const slotTime = new Date(today.getTime() + hour * 60 * 60 * 1000 + min * 60 * 1000); if (slotTime > now) { timeSlots.push({ time: slotTime, label: slotTime.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }) }); } } } const timeSlotsHtml = timeSlots.slice(0, 12).map(slot => ` `).join(''); const content = `
${timeSlotsHtml}
`; GoingsOn.ui.openModal('Schedule Time Block', content); } /** * Select a quick time slot and update the datetime input. * @param {HTMLElement} btn - Clicked button element * @param {string} isoTime - ISO 8601 timestamp for the slot */ function selectTimeSlot(btn, isoTime) { document.querySelectorAll('.time-block-quick-btn').forEach(b => b.classList.remove('selected')); btn.classList.add('selected'); const datetime = new Date(isoTime); document.getElementById('schedule-datetime').value = datetime.toISOString().slice(0, 16); } /** * Select a duration preset and update the hidden input. * @param {HTMLElement} btn - Clicked button element * @param {number} minutes - Duration in minutes */ function selectDuration(btn, minutes) { document.querySelectorAll('.duration-preset').forEach(b => b.classList.remove('selected')); btn.classList.add('selected'); document.getElementById('schedule-duration').value = minutes; } /** * Submit the schedule task modal, creating the time block. * @param {string} id - Task ID to schedule */ async function scheduleTaskFromModal(id) { const datetimeInput = document.getElementById('schedule-datetime'); const durationInput = document.getElementById('schedule-duration'); if (!datetimeInput.value) { GoingsOn.ui.showToast('Please select a time', 'error'); return; } const startTime = new Date(datetimeInput.value).toISOString(); const duration = parseInt(durationInput.value) || 30; try { await GoingsOn.api.dayPlanning.scheduleTask(id, { startTime, duration }); GoingsOn.ui.closeModal(); GoingsOn.tasks.load(); const dayPlanView = document.getElementById('day-plan-view'); if (dayPlanView && !dayPlanView.classList.contains('hidden')) { GoingsOn.dayPlan.load(); } const startDisplay = new Date(datetimeInput.value).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }); const endDisplay = new Date(new Date(datetimeInput.value).getTime() + duration * 60000).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }); GoingsOn.ui.showToast(`Task scheduled for ${startDisplay} \u2013 ${endDisplay}`, 'success'); } catch (err) { GoingsOn.ui.showToast('Failed to schedule task: ' + err, 'error'); } } // ============ Daily Review ============ const formatDateForApi = GoingsOn.utils.formatDateForApi; const formatDateDisplay = GoingsOn.utils.formatDateDisplay; const REFLECTION_PROMPTS = [ { key: 'went-well' }, { key: 'improve' }, ]; function setupDailyReviewAutoSave() { GoingsOn.planReviewToggle.wireReflectionAutosave({ idPrefix: 'daily', prompts: REFLECTION_PROMPTS, onChange: (values) => { const dateStr = formatDateForApi(GoingsOn.state.dayPlanDate); GoingsOn.api.dailyNotes.upsert({ noteDate: dateStr, wentWell: values['went-well'], couldImprove: values['improve'], isReviewed: false, }).catch(() => {}); }, }); } /** * Render the "Accomplished" list inline in the day plan sidebar, * and refresh nudge state. */ async function loadDayReviewPane() { const container = document.getElementById('day-accomplished-inline'); if (!container) return; const timelineItems = GoingsOn.state.dayPlanData?.timelineItems || []; const completedTasks = timelineItems.filter(item => item.itemType === 'task'); const eventCount = timelineItems.filter(item => item.itemType === 'event').length; const dateStr = formatDateForApi(GoingsOn.state.dayPlanDate); let isReviewed = false; try { const saved = await GoingsOn.api.dailyNotes.get(dateStr); if (saved) isReviewed = saved.isReviewed || false; } catch (err) { console.error('Failed to load daily note:', err); } if (completedTasks.length === 0 && eventCount === 0) { container.innerHTML = ''; } else { const statBits = []; if (completedTasks.length > 0) { statBits.push(`${completedTasks.length} task${completedTasks.length === 1 ? '' : 's'}`); } if (eventCount > 0) { statBits.push(`${eventCount} event${eventCount === 1 ? '' : 's'}`); } const statStrip = `
${statBits.join(' ยท ')}
`; const completedList = completedTasks.map(t => `
  • ${esc(t.title)} ${t.projectName ? `${esc(t.projectName)}` : ''}
  • `).join(''); container.innerHTML = ` ${statStrip} ${completedTasks.length > 0 ? `` : ''} `; } renderFinishReviewBar(dateStr, isReviewed); GoingsOn.planReviewToggle.setStatusBadge( 'day-review-status-badge', dayPeriodState(dateStr), isReviewed, ); const isToday = dateStr === formatDateForApi(new Date()); updateDayNudges(isReviewed, isToday); } /** * Returns 'past', 'current', or 'future' for the currently viewed date. */ function dayPeriodState(dateStr) { const todayStr = formatDateForApi(new Date()); if (dateStr === todayStr) return 'current'; return dateStr < todayStr ? 'past' : 'future'; } function renderFinishReviewBar(dateStr, isReviewed) { const bar = document.querySelector('#day-plan-view .finish-review-bar'); if (!bar) return; const state = dayPeriodState(dateStr); if (state === 'future') { bar.classList.add('hidden'); bar.innerHTML = ''; return; } bar.classList.remove('hidden'); if (state === 'current') { bar.innerHTML = ` `; } else { const label = isReviewed ? 'View Past Review' : 'Review Past Day'; bar.innerHTML = ` `; } } function updateDayNudges(isReviewed, isToday) { const settings = GoingsOn.planReviewToggle.getSettings(); if (isToday && settings.reviewNudges && !isReviewed && GoingsOn.planReviewToggle.isAfterWorkHours()) { GoingsOn.planReviewToggle.updateDot('day', true); } else { GoingsOn.planReviewToggle.updateDot('day', false); } } /** * Open the end-of-day reflection modal. */ async function openFinishReviewModal() { const dateStr = formatDateForApi(GoingsOn.state.dayPlanDate); const displayDate = formatDateDisplay(GoingsOn.state.dayPlanDate); let wentWell = ''; let improve = ''; let isReviewed = false; try { const saved = await GoingsOn.api.dailyNotes.get(dateStr); if (saved) { wentWell = saved.wentWell || ''; improve = saved.couldImprove || ''; isReviewed = saved.isReviewed || false; } } catch (err) { console.error('Failed to load daily note:', err); } const reflectionHtml = GoingsOn.planReviewToggle.renderReflection({ idPrefix: 'daily', prompts: [ { key: 'went-well', label: 'What went well today?', placeholder: 'Got focused work done in the morning...', value: wentWell }, { key: 'improve', label: 'What could be improved?', placeholder: 'Got distracted after lunch...', value: improve }, ], }); const isPast = dayPeriodState(dateStr) === 'past'; const banner = isPast ? `
    You are reviewing a past day (${esc(displayDate)}), not today.
    ` : ''; const content = `
    ${banner} ${reflectionHtml}
    `; const title = isPast ? `Reviewing Past: ${displayDate}` : `Wrap Up: ${displayDate}`; GoingsOn.ui.openModal(title, content); requestAnimationFrame(() => { setupDailyReviewAutoSave(); GoingsOn.planReviewToggle.autoGrowReflection({ idPrefix: 'daily', prompts: REFLECTION_PROMPTS, }); }); } async function saveDailyReview() { const dateStr = formatDateForApi(GoingsOn.state.dayPlanDate); const wentWellInput = document.getElementById('daily-went-well'); const improveInput = document.getElementById('daily-improve'); try { await GoingsOn.api.dailyNotes.upsert({ noteDate: dateStr, wentWell: wentWellInput?.value?.trim() || '', couldImprove: improveInput?.value?.trim() || '', isReviewed: true, }); GoingsOn.ui.closeModal(); GoingsOn.ui.showToast('Daily review saved!', 'success'); updateDayNudges(true, true); } catch (err) { GoingsOn.ui.showToast('Failed to save daily review: ' + err, 'error'); } } // ============ Populate GoingsOn.dayPlanSchedule Namespace ============ GoingsOn.dayPlanSchedule = { openScheduleTaskModal, selectTimeSlot, selectDuration, scheduleTaskFromModal, openFinishReviewModal, saveDailyReview, loadDayReviewPane, }; })();