feat: task tags, sort controls, and tag filtering

- Added tags (JSONB array) to tasks schema with full CRUD support
- Tag editor in TaskDetailPanel with chip UI, Enter/comma to add, Backspace to remove
- Tag badges on TaskCard, KanbanBoard cards, and DashboardPage
- Sort controls on QueuePage: sort by priority, due date, created, updated, name
- Sort direction toggle (asc/desc) with persistence to localStorage
- Tag filter dropdown in QueuePage header (populated from existing tags)
- Search now matches tags
- Backend: tags in create/update, progressNotes in PATCH body
This commit is contained in:
2026-01-29 11:04:39 +00:00
parent f4c60bf6aa
commit e9c0763025
7 changed files with 185 additions and 12 deletions

View File

@@ -154,6 +154,7 @@ export const taskRoutes = new Elysia({ prefix: "/api/tasks" })
taskNumber: nextNumber,
projectId: body.projectId || null,
dueDate: body.dueDate ? new Date(body.dueDate) : null,
tags: body.tags || [],
subtasks: [],
progressNotes: [],
})
@@ -193,6 +194,7 @@ export const taskRoutes = new Elysia({ prefix: "/api/tasks" })
),
projectId: t.Optional(t.Union([t.String(), t.Null()])),
dueDate: t.Optional(t.Union([t.String(), t.Null()])),
tags: t.Optional(t.Array(t.String())),
}),
}
)
@@ -240,7 +242,9 @@ export const taskRoutes = new Elysia({ prefix: "/api/tasks" })
if (body.assigneeName !== undefined) updates.assigneeName = body.assigneeName;
if (body.projectId !== undefined) updates.projectId = body.projectId;
if (body.dueDate !== undefined) updates.dueDate = body.dueDate ? new Date(body.dueDate) : null;
if (body.tags !== undefined) updates.tags = body.tags;
if (body.subtasks !== undefined) updates.subtasks = body.subtasks;
if (body.progressNotes !== undefined) updates.progressNotes = body.progressNotes;
const updated = await db
.update(tasks)
@@ -269,6 +273,7 @@ export const taskRoutes = new Elysia({ prefix: "/api/tasks" })
assigneeName: t.Optional(t.Union([t.String(), t.Null()])),
projectId: t.Optional(t.Union([t.String(), t.Null()])),
dueDate: t.Optional(t.Union([t.String(), t.Null()])),
tags: t.Optional(t.Array(t.String())),
subtasks: t.Optional(t.Array(t.Object({
id: t.String(),
title: t.String(),
@@ -276,6 +281,10 @@ export const taskRoutes = new Elysia({ prefix: "/api/tasks" })
completedAt: t.Optional(t.String()),
createdAt: t.String(),
}))),
progressNotes: t.Optional(t.Array(t.Object({
timestamp: t.String(),
note: t.String(),
}))),
}),
}
)