import * as api from '../api.js';
import { showModal, closeModal } from '../components/modal.js';
const el = () => document.getElementById('page-tasks');
let currentFilter = null;
let selectedTaskId = null;
export function initTasks() {
el().innerHTML = `
Tasks
`;
renderFilters();
document.getElementById('btn-new-task').addEventListener('click', () => showNewTaskModal());
loadTasks();
}
function renderFilters() {
const statuses = [null, 'Pending', 'Active', 'Paused', 'Completed', 'Abandoned'];
const labels = ['All', 'Pending', 'Active', 'Paused', 'Completed', 'Abandoned'];
const container = document.getElementById('task-filters');
container.innerHTML = statuses.map((s, i) => `
${labels[i]} `).join('');
container.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', () => {
currentFilter = btn.dataset.status || null;
renderFilters();
loadTasks();
});
});
}
async function loadTasks() {
try {
const tasks = await api.tasks.list(currentFilter);
renderTaskList(tasks);
} catch (e) {
document.getElementById('task-list').innerHTML = `Failed to load tasks
`;
}
}
function renderTaskList(tasks) {
const container = document.getElementById('task-list');
document.getElementById('task-detail').classList.add('hidden');
container.classList.remove('hidden');
if (!tasks.length) {
container.innerHTML = `No tasks found
`;
return;
}
container.innerHTML = tasks.map(t => {
const subCount = t.subTasks ? t.subTasks.length : 0;
return `
${t.status}
${esc(t.title)}
${subCount > 0 ? `${subCount} subtask${subCount !== 1 ? 's' : ''} ` : ''}
${t.category ? esc(t.category) + ' · ' : ''}${formatDate(t.createdAt)}
`;
}).join('');
container.querySelectorAll('.task-item').forEach(item => {
item.addEventListener('click', () => showTaskDetail(parseInt(item.dataset.id)));
});
}
async function buildBreadcrumbs(task) {
const trail = [{ id: task.id, title: task.title }];
let current = task;
while (current.parentTaskId) {
try {
current = await api.tasks.get(current.parentTaskId);
trail.unshift({ id: current.id, title: current.title });
} catch {
break;
}
}
return trail;
}
async function showTaskDetail(id) {
selectedTaskId = id;
try {
const task = await api.tasks.get(id);
const container = document.getElementById('task-detail');
document.getElementById('task-list').classList.add('hidden');
container.classList.remove('hidden');
// Build breadcrumb trail
const breadcrumbs = await buildBreadcrumbs(task);
const breadcrumbHtml = breadcrumbs.length > 1
? `${breadcrumbs.map((b, i) =>
i < breadcrumbs.length - 1
? `
${esc(b.title)} / `
: `
${esc(b.title)} `
).join('')}
`
: '';
container.innerHTML = `
← ${task.parentTaskId ? 'Back to parent' : 'Back to list'}
${breadcrumbHtml}
${task.description ? `
${esc(task.description)}
` : ''}
Subtasks
${task.status !== 'Completed' && task.status !== 'Abandoned' ? `
+ Add Subtask ` : ''}
Notes
${task.contextEvents && task.contextEvents.length ? `
Linked Context Events
App Title Time
${task.contextEvents.slice(0, 50).map(e => `
${esc(e.appName)}
${esc(e.windowTitle)}
${formatTime(e.timestamp)}
`).join('')}
` : ''}
`;
// Render action buttons based on status
renderActions(task);
// Render subtasks
renderSubTasks(task.subTasks || []);
// Render notes
renderNotes(task.notes || []);
// Breadcrumb navigation
container.querySelectorAll('.breadcrumb-link').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
showTaskDetail(parseInt(link.dataset.id));
});
});
// Back button
document.getElementById('btn-back-tasks').addEventListener('click', () => {
if (task.parentTaskId) {
showTaskDetail(task.parentTaskId);
} else {
container.classList.add('hidden');
document.getElementById('task-list').classList.remove('hidden');
loadTasks();
}
});
// Add subtask button
const addSubBtn = document.getElementById('btn-add-subtask');
if (addSubBtn) {
addSubBtn.addEventListener('click', () => showNewTaskModal(task.id));
}
// Add note
const noteInput = document.getElementById('note-input');
document.getElementById('btn-add-note').addEventListener('click', () => addNote(task.id, noteInput));
noteInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') addNote(task.id, noteInput);
});
} catch (e) {
alert('Failed to load task: ' + e.message);
}
}
function renderSubTasks(subTasks) {
const container = document.getElementById('task-subtasks');
if (!subTasks.length) {
container.innerHTML = `No subtasks
`;
return;
}
container.innerHTML = subTasks.map(st => {
const subCount = st.subTasks ? st.subTasks.length : 0;
const canStart = st.status === 'Pending' || st.status === 'Paused';
const canComplete = st.status === 'Active' || st.status === 'Paused';
return `
${canStart ? `${st.status === 'Paused' ? 'Resume' : 'Start'} ` : ''}
${canComplete ? `Complete ` : ''}
`;
}).join('');
// Navigate to subtask detail
container.querySelectorAll('.subtask-item-title').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
showTaskDetail(parseInt(link.dataset.id));
});
});
// Inline subtask actions
container.querySelectorAll('.subtask-action').forEach(btn => {
btn.addEventListener('click', async (e) => {
e.stopPropagation();
const action = btn.dataset.action;
const stId = parseInt(btn.dataset.id);
try {
if (action === 'start') await api.tasks.start(stId);
else if (action === 'complete') await api.tasks.complete(stId);
showTaskDetail(selectedTaskId);
} catch (err) { alert(err.message); }
});
});
}
function renderActions(task) {
const container = document.getElementById('task-actions');
const actions = [];
switch (task.status) {
case 'Pending':
actions.push({ label: 'Start', cls: 'btn-success', action: () => api.tasks.start(task.id) });
actions.push({ label: 'Abandon', cls: 'btn-danger', action: () => api.tasks.abandon(task.id) });
break;
case 'Active':
actions.push({ label: 'Pause', cls: 'btn-warning', action: () => api.tasks.pause(task.id) });
actions.push({ label: 'Complete', cls: 'btn-success', action: () => api.tasks.complete(task.id) });
actions.push({ label: 'Abandon', cls: 'btn-danger', action: () => api.tasks.abandon(task.id) });
break;
case 'Paused':
actions.push({ label: 'Resume', cls: 'btn-success', action: () => api.tasks.resume(task.id) });
actions.push({ label: 'Complete', cls: 'btn-success', action: () => api.tasks.complete(task.id) });
actions.push({ label: 'Abandon', cls: 'btn-danger', action: () => api.tasks.abandon(task.id) });
break;
}
container.innerHTML = '';
actions.forEach(({ label, cls, action }) => {
const btn = document.createElement('button');
btn.className = `btn btn-sm ${cls}`;
btn.textContent = label;
btn.addEventListener('click', async () => {
try {
await action();
showTaskDetail(task.id);
} catch (e) { alert(e.message); }
});
container.appendChild(btn);
});
}
function renderNotes(notes) {
const container = document.getElementById('task-notes');
if (!notes.length) {
container.innerHTML = `No notes yet
`;
return;
}
container.innerHTML = notes.map(n => `
`).join('');
}
async function addNote(taskId, input) {
const content = input.value.trim();
if (!content) return;
try {
await api.notes.create(taskId, { content, type: 'General' });
input.value = '';
showTaskDetail(taskId);
} catch (e) { alert(e.message); }
}
function showNewTaskModal(parentTaskId = null) {
const title = parentTaskId ? 'New Subtask' : 'New Task';
showModal(title, `
Title *
Description
Category
`,
[
{ label: 'Cancel', onClick: () => {} },
{
label: 'Create', cls: 'btn-primary', onClick: async (modal) => {
const taskTitle = modal.querySelector('#new-task-title').value.trim();
if (!taskTitle) { alert('Title is required'); throw new Error('cancel'); }
const description = modal.querySelector('#new-task-desc').value.trim() || null;
const category = modal.querySelector('#new-task-cat').value.trim() || null;
const body = { title: taskTitle, description, category };
if (parentTaskId) body.parentTaskId = parentTaskId;
await api.tasks.create(body);
if (parentTaskId) {
showTaskDetail(parentTaskId);
} else {
loadTasks();
}
},
},
]);
setTimeout(() => document.getElementById('new-task-title')?.focus(), 100);
}
function formatDate(iso) {
return new Date(iso).toLocaleDateString();
}
function formatDateTime(iso) {
const d = new Date(iso);
return d.toLocaleDateString() + ' ' + d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}
function formatTime(iso) {
return new Date(iso).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}
function esc(str) {
if (!str) return '';
const d = document.createElement('div');
d.textContent = str;
return d.innerHTML;
}