// Project view — header + tasks (with inline status toggle) + pages + refs + add-task form. import { api } from '../api.js'; import { el, mount, clear } from '../dom.js'; const STATUSES = ['todo', 'doing', 'blocked', 'done']; const STATUS_CLASS = { todo: 'idle', doing: 'warn', blocked: 'bad', done: 'ok' }; function nextStatus(s) { const i = STATUSES.indexOf(s); return STATUSES[(i + 1) % STATUSES.length]; } function taskRow(t, onChange) { const badge = el('span', { class: 'status ' + (STATUS_CLASS[t.status] || 'idle'), style: { cursor: 'pointer' }, title: 'click to advance status', onclick: async () => { const ns = nextStatus(t.status); try { const updated = await api.patch('/api/tasks/' + t.id, { status: ns }); onChange(updated); } catch (e) { alert('update failed: ' + e.message); } } }, t.status); return el('li', {}, badge, ' ', t.title); } function pageRow(p) { return el('li', {}, el('a', { href: '#/page/' + p.id }, p.title)); } export async function render(main, ctx) { const id = ctx.params.id; mount(main, el('p', { class: 'view-sub muted' }, 'Loading …')); let proj; try { proj = await api.get('/api/projects/' + id); } catch (e) { mount(main, el('h1', { class: 'view-h1' }, 'Project not found'), el('p', { class: 'view-sub muted' }, e.message) ); return; } const [tasks, pages] = await Promise.all([ api.get(`/api/projects/${id}/tasks`).catch(() => []), api.get(`/api/spaces/${proj.space_id}/pages`).catch(() => []) ]); const tasksList = el('ul', { class: 'plain' }); function rerenderTasks(rows) { clear(tasksList); if (!rows.length) tasksList.appendChild(el('li', { class: 'muted' }, '(no tasks)')); for (const t of rows) { tasksList.appendChild(taskRow(t, (updated) => { const idx = rows.findIndex(r => r.id === updated.id); if (idx >= 0) rows[idx] = updated; rerenderTasks(rows); })); } } rerenderTasks(tasks); const newTitle = el('input', { type: 'text', placeholder: 'New task title', style: { flex: 1 } }); async function addTask() { const title = newTitle.value.trim(); if (!title) return; try { const created = await api.post(`/api/spaces/${proj.space_id}/tasks`, { title, project_id: proj.id }); newTitle.value = ''; tasks.unshift(created); rerenderTasks(tasks); } catch (e) { alert('add failed: ' + e.message); } } newTitle.addEventListener('keydown', (e) => { if (e.key === 'Enter') addTask(); }); mount(main, el('h1', { class: 'view-h1' }, proj.name), el('p', { class: 'view-sub' }, el('span', { class: 'status ' + (proj.status === 'done' ? 'ok' : proj.status === 'paused' ? 'warn' : '') }, proj.status), proj.started_at ? ' · started ' + new Date(proj.started_at).toISOString().slice(0, 10) : '', proj.completed_at ? ' · completed ' + new Date(proj.completed_at).toISOString().slice(0, 10) : '' ), proj.description ? el('p', {}, proj.description) : null, el('div', { class: 'row' }, el('div', { class: 'card' }, el('h3', {}, 'Tasks'), tasksList, el('div', { style: { display: 'flex', gap: '8px', marginTop: '10px' } }, newTitle, el('button', { class: 'primary', onclick: addTask }, 'Add') ) ), el('div', { class: 'card' }, el('h3', {}, 'Pages in space'), pages.length ? el('ul', { class: 'plain' }, pages.slice(0, 10).map(pageRow)) : el('p', { class: 'muted' }, 'No pages.') ) ) ); }