feat(ui): space + project + home views
Home: recent activity feed from /api/audit/actor?limit=20 with relative timestamps and entity-typed links into detail views. Space: header + three-column row of Projects / Open tasks (status=todo) / Recent pages + refs cards. Status badges on projects and tasks use the shared .status palette. Project: header (status + start/complete dates), Tasks card with inline status badges that cycle todo->doing->blocked->done on click (PATCH /api/tasks/:id), Pages in space card, Add-task inline form bound to project_id. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,80 @@
|
||||
// T17 stub — full implementation lands in T19.
|
||||
// Space view — header + three columns (projects, recent open tasks, recent pages/refs).
|
||||
import { api } from '../api.js';
|
||||
import { el, mount } from '../dom.js';
|
||||
export async function render(main, ctx) {
|
||||
mount(main,
|
||||
el('h1', { class: 'view-h1' }, 'Space'),
|
||||
el('p', { class: 'view-sub muted' }, 'id: ' + (ctx.params.id || '—')),
|
||||
el('div', { class: 'card muted' }, 'Space view ships in T19.')
|
||||
|
||||
function projCard(p) {
|
||||
return el('li', {},
|
||||
el('a', { href: '#/project/' + p.id }, p.name),
|
||||
' ',
|
||||
el('span', { class: 'status' + (p.status === 'done' ? ' ok' : p.status === 'paused' ? ' warn' : '') }, p.status)
|
||||
);
|
||||
}
|
||||
|
||||
function taskCard(t) {
|
||||
return el('li', {},
|
||||
el('span', { class: 'status' + (t.status === 'blocked' ? ' bad' : '') }, t.status),
|
||||
' ', t.title
|
||||
);
|
||||
}
|
||||
|
||||
function pageCard(p) {
|
||||
return el('li', {}, el('a', { href: '#/page/' + p.id }, p.title));
|
||||
}
|
||||
|
||||
function refCard(r) {
|
||||
return el('li', {}, el('a', { href: '#/ref/' + r.id }, r.title || r.source_url || '(untitled)'),
|
||||
' ', el('span', { class: 'status idle' }, r.kind));
|
||||
}
|
||||
|
||||
export async function render(main, ctx) {
|
||||
const id = ctx.params.id;
|
||||
mount(main,
|
||||
el('h1', { class: 'view-h1' }, 'Space'),
|
||||
el('p', { class: 'view-sub muted' }, 'Loading …')
|
||||
);
|
||||
|
||||
let space;
|
||||
try { space = await api.get('/api/spaces/' + id); }
|
||||
catch (e) {
|
||||
mount(main,
|
||||
el('h1', { class: 'view-h1' }, 'Space not found'),
|
||||
el('p', { class: 'view-sub muted' }, e.message)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const [projects, tasks, pages, refs] = await Promise.all([
|
||||
api.get(`/api/spaces/${id}/projects`).catch(() => []),
|
||||
api.get(`/api/spaces/${id}/tasks?status=todo`).catch(() => []),
|
||||
api.get(`/api/spaces/${id}/pages`).catch(() => []),
|
||||
api.get(`/api/refs?space_id=${id}&limit=8`).catch(() => [])
|
||||
]);
|
||||
|
||||
mount(main,
|
||||
el('h1', { class: 'view-h1' }, space.name),
|
||||
el('p', { class: 'view-sub' }, space.description || el('span', { class: 'muted' }, 'No description.')),
|
||||
el('div', { class: 'row' },
|
||||
el('div', { class: 'card' },
|
||||
el('h3', {}, 'Projects'),
|
||||
projects.length
|
||||
? el('ul', { class: 'plain' }, projects.map(projCard))
|
||||
: el('p', { class: 'muted' }, 'None yet.')
|
||||
),
|
||||
el('div', { class: 'card' },
|
||||
el('h3', {}, 'Open tasks'),
|
||||
tasks.length
|
||||
? el('ul', { class: 'plain' }, tasks.slice(0, 10).map(taskCard))
|
||||
: el('p', { class: 'muted' }, 'Clear board.')
|
||||
),
|
||||
el('div', { class: 'card' },
|
||||
el('h3', {}, 'Recent pages & refs'),
|
||||
pages.length || refs.length
|
||||
? el('ul', { class: 'plain' }, [
|
||||
...pages.slice(0, 6).map(pageCard),
|
||||
...refs.slice(0, 6).map(refCard)
|
||||
])
|
||||
: el('p', { class: 'muted' }, 'Nothing here yet.')
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user