/**
* GoingsOn - Task Overview Module
* Full detail view for a task: metadata, subtasks, time sessions, annotations.
* For recurring tasks: completion heatmap and streak stats.
*/
(function() {
'use strict';
const esc = GoingsOn.utils.escapeHtml;
const escAttr = GoingsOn.utils.escapeAttr;
let currentTaskId = null;
let heatmapMonth = null; // Date object for displayed month
// ============ Open / Close ============
/**
* Phase 7 Tier 6 — task detail now opens as a right-side drawer overlay
* on top of whichever view the user is currently in (task list, contact
* dashboard, day plan...). The drawer reuses the same render() output as
* the old full-page #task-overview-view, which is retained for cases
* where something explicitly navigates to it (router deep links).
*/
async function open(taskId) {
currentTaskId = taskId;
heatmapMonth = new Date();
const drawer = document.getElementById('task-detail-drawer');
const content = document.getElementById('task-drawer-content');
if (!drawer || !content) {
// Drawer not mounted (older index.html?) — fall back to legacy
// full-page view so we never break the flow.
return openLegacy(taskId);
}
drawer.classList.add('visible');
drawer.setAttribute('aria-hidden', 'false');
document.addEventListener('keydown', handleDrawerKeydown);
content.innerHTML = '
`;
}
}
/**
* Legacy fallback: navigate to the full-page #task-overview-view. Used
* only when the drawer element isn't in the DOM. Keeps deep-link routes
* working through the transition.
*/
async function openLegacy(taskId) {
GoingsOn.navigation.switchView('task-overview');
const content = document.getElementById('task-overview-content');
content.innerHTML = '
';
return html;
}
// ============ Annotations ============
function renderAnnotations(t) {
let html = '
';
html += `
Notes ${t.annotations.length}
`;
if (t.annotations.length > 0) {
html += '
';
for (const a of t.annotations) {
const date = new Date(a.timestamp);
const dateStr = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' });
html += `
`;
html += `
${esc(dateStr)}
`;
html += `
${esc(a.note)}
`;
html += '
';
}
html += '
';
}
// Add note form
html += `
`;
html += '
';
return html;
}
async function addNote() {
const input = document.getElementById('overview-new-note');
const text = input?.value?.trim();
if (!text) return;
try {
await GoingsOn.api.annotations.add(currentTaskId, text);
input.value = '';
GoingsOn.cache.invalidate('tasks');
if (currentTaskId) open(currentTaskId);
} catch (err) {
GoingsOn.ui.showToast('Failed to add note', 'error');
}
}
// ============ Actions ============
function renderActions(t) {
let html = '';
if (t.status !== 'Completed') {
html += ` `;
}
html += ` `;
html += ``;
return html;
}
async function completeTask() {
if (!currentTaskId) return;
try {
await GoingsOn.api.tasks.complete(currentTaskId);
GoingsOn.cache.invalidate('tasks');
GoingsOn.ui.showToast('Task completed!', 'success');
open(currentTaskId); // Refresh to show updated state
} catch (err) {
GoingsOn.ui.showToast('Failed to complete task', 'error');
}
}
async function deleteTask() {
if (!currentTaskId) return;
if (!await GoingsOn.ui.confirmDelete('task')) return;
try {
await GoingsOn.api.tasks.delete(currentTaskId);
GoingsOn.cache.invalidate('tasks');
GoingsOn.ui.showToast('Task deleted', 'success');
close();
} catch (err) {
GoingsOn.ui.showToast('Failed to delete task', 'error');
}
}
// ============ Namespace ============
GoingsOn.taskOverview = {
open,
close,
prevMonth,
nextMonth,
toggleSubtask,
addSubtask,
addNote,
completeTask,
deleteTask,
};
})();