From 787752bacdb85daf75a79ee2b85eafb6d130a817 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 21 Jul 2026 15:24:51 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B1=95=E7=A4=BA=20TaskTree=20?= =?UTF-8?q?=E5=88=86=E7=BB=84=E5=AE=8C=E6=88=90=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/tasktree/contracts.ts | 1 + internal/tasktree/store.ts | 3 +- internal/tasktree/tasktree.test.ts | 1 + web/hwlab-cloud-web/src/api/tasktree.ts | 2 +- .../src/views/projects/TaskTreeView.vue | 45 ++++++++++++++++--- 5 files changed, 43 insertions(+), 9 deletions(-) diff --git a/internal/tasktree/contracts.ts b/internal/tasktree/contracts.ts index 70c0fe1f..8a6b0dee 100644 --- a/internal/tasktree/contracts.ts +++ b/internal/tasktree/contracts.ts @@ -54,6 +54,7 @@ export type TaskGroupOverview = { taskCount: number; subtaskCount: number; subsubtaskCount: number; + completedCount: number; reportCount: number; startAt: string | null; dueAt: string | null; diff --git a/internal/tasktree/store.ts b/internal/tasktree/store.ts index b5e385f3..bcefe04b 100644 --- a/internal/tasktree/store.ts +++ b/internal/tasktree/store.ts @@ -145,6 +145,7 @@ export class TaskTreeStore { COUNT(DISTINCT t.id) FILTER (WHERE t.kind='task')::int AS task_count, COUNT(DISTINCT t.id) FILTER (WHERE t.kind='subtask')::int AS subtask_count, COUNT(DISTINCT t.id) FILTER (WHERE t.kind='subsubtask')::int AS subsubtask_count, + COUNT(DISTINCT t.id) FILTER (WHERE t.status='completed')::int AS completed_count, COUNT(DISTINCT r.id)::int AS report_count, MIN(CASE WHEN t.id IS NULL THEN NULL @@ -696,7 +697,7 @@ async function schemaIsCurrent(pool: pg.Pool): Promise { } function groupRow(row: any): TaskGroup { return { id: row.id, name: row.name, description: row.description, createdAt: iso(row.created_at), updatedAt: iso(row.updated_at) }; } -function overviewRow(row: any): TaskGroupOverview { return { group: groupRow(row), taskCount: Number(row.task_count), subtaskCount: Number(row.subtask_count), subsubtaskCount: Number(row.subsubtask_count), reportCount: Number(row.report_count), startAt: nullableIso(row.start_at), dueAt: nullableIso(row.due_at) }; } +function overviewRow(row: any): TaskGroupOverview { return { group: groupRow(row), taskCount: Number(row.task_count), subtaskCount: Number(row.subtask_count), subsubtaskCount: Number(row.subsubtask_count), completedCount: Number(row.completed_count), reportCount: Number(row.report_count), startAt: nullableIso(row.start_at), dueAt: nullableIso(row.due_at) }; } function taskRow(row: any): TaskItem { return { id: row.id, groupId: row.group_id, parentId: row.parent_id, kind: row.kind, title: row.title, description: row.description, status: row.status, startAt: nullableIso(row.start_at), dueAt: nullableIso(row.due_at), sortOrder: row.sort_order, createdAt: iso(row.created_at), updatedAt: iso(row.updated_at) }; } function milestoneRow(row: any): Milestone { return { id: row.id, groupId: row.group_id, taskId: row.task_id, title: row.title, occursAt: iso(row.occurs_at), createdAt: iso(row.created_at) }; } function reportRow(row: any): ExecutionReport { return { id: row.id, taskId: row.task_id, title: row.title, body: row.body, status: row.status, createdAt: iso(row.created_at) }; } diff --git a/internal/tasktree/tasktree.test.ts b/internal/tasktree/tasktree.test.ts index 99f4b7d0..fb10c3ee 100644 --- a/internal/tasktree/tasktree.test.ts +++ b/internal/tasktree/tasktree.test.ts @@ -217,6 +217,7 @@ test("native PostgreSQL store exposes overview and three task levels", async () assert.equal(overview?.taskCount, 1); assert.equal(overview?.subtaskCount, 3); assert.equal(overview?.subsubtaskCount, 1); + assert.equal(overview?.completedCount, 1); assert.equal(overview?.reportCount, 1); assert.equal(overview?.startAt, new Date(timelineStart).toISOString()); assert.equal(overview?.dueAt, new Date(timelineDue).toISOString()); diff --git a/web/hwlab-cloud-web/src/api/tasktree.ts b/web/hwlab-cloud-web/src/api/tasktree.ts index d8a6e701..c47287a1 100644 --- a/web/hwlab-cloud-web/src/api/tasktree.ts +++ b/web/hwlab-cloud-web/src/api/tasktree.ts @@ -6,7 +6,7 @@ export type TaskTreeTask = { id: string; groupId: string; parentId: string | nul export type TaskTreeMilestone = { id: string; groupId: string; taskId: string | null; title: string; occursAt: string; createdAt: string }; export type TaskTreeReport = { id: string; taskId: string; title: string; body: string; status: string; createdAt: string }; export type TaskTreeTimeline = { group: TaskTreeGroup; tasks: TaskTreeTask[]; milestones: TaskTreeMilestone[]; reports: TaskTreeReport[] }; -export type TaskTreeGroupOverview = { group: TaskTreeGroup; taskCount: number; subtaskCount: number; subsubtaskCount: number; reportCount: number; startAt: string | null; dueAt: string | null }; +export type TaskTreeGroupOverview = { group: TaskTreeGroup; taskCount: number; subtaskCount: number; subsubtaskCount: number; completedCount: number; reportCount: number; startAt: string | null; dueAt: string | null }; type Result = { ok?: boolean; data?: T; error?: { code?: string; message?: string } }; export const tasktreeAPI = { diff --git a/web/hwlab-cloud-web/src/views/projects/TaskTreeView.vue b/web/hwlab-cloud-web/src/views/projects/TaskTreeView.vue index 50b6a46e..3c113b16 100644 --- a/web/hwlab-cloud-web/src/views/projects/TaskTreeView.vue +++ b/web/hwlab-cloud-web/src/views/projects/TaskTreeView.vue @@ -1,7 +1,7 @@