feat: add task comments feature with CRUD operations

- Introduced a new `task_comments` table in the database schema.
- Implemented task comments API endpoints for listing, creating, and deleting comments.
- Enhanced the task detail dialog to display comments and allow users to add new comments.
- Updated task row component to handle click events for viewing task details.
- Added a theme provider to manage light/dark mode across the application.
- Refactored Milkdown editor to use Crepe for improved markdown editing experience.
- Updated global styles to accommodate new editor and theme changes.
- Enhanced task filtering and sorting functionality in the tasks page.
This commit is contained in:
Roberto Musso
2026-02-23 12:54:14 +01:00
parent 98acf6220e
commit c1aa6829c9
24 changed files with 996 additions and 234 deletions

View File

@@ -3,8 +3,9 @@ import { z } from 'zod';
import { eq, asc, inArray, and, or, like, sql } from 'drizzle-orm';
import { alias } from 'drizzle-orm/sqlite-core';
import { getDb } from '../db';
import { clients, projects, tasks, checkpoints, notes } from '../db/schema';
import { clients, projects, tasks, checkpoints, notes, taskComments } from '../db/schema';
import { getStore } from '../store';
import { saveTokenAndInit, hasActiveToken } from '../ai/provider';
const t = initTRPC.create();
@@ -199,12 +200,13 @@ const tasksRouter = router({
: undefined,
);
const orderByClause =
const priorityExpr = sql`CASE ${tasks.priority} WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 ELSE 4 END`;
const orderByClauses =
input?.orderBy === 'dueDate'
? asc(tasks.dueDate)
? [asc(tasks.dueDate), asc(priorityExpr)]
: input?.orderBy === 'priority'
? asc(sql`CASE ${tasks.priority} WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 ELSE 4 END`)
: asc(tasks.createdAt);
? [asc(priorityExpr), asc(tasks.dueDate)]
: [asc(tasks.dueDate), asc(priorityExpr)];
return db
.select({
@@ -226,7 +228,7 @@ const tasksRouter = router({
.leftJoin(clients, eq(projects.clientId, clients.id))
.leftJoin(parentClients, eq(clients.parentId, parentClients.id))
.where(conditions)
.orderBy(orderByClause)
.orderBy(...orderByClauses)
.all();
}),
@@ -436,6 +438,41 @@ const notesRouter = router({
}),
});
const taskCommentsRouter = router({
list: publicProcedure
.input(z.object({ taskId: z.string() }))
.query(({ input }) => {
return getDb()
.select()
.from(taskComments)
.where(eq(taskComments.taskId, input.taskId))
.orderBy(asc(taskComments.createdAt))
.all();
}),
create: publicProcedure
.input(z.object({ taskId: z.string(), author: z.string(), content: z.string() }))
.mutation(({ input }) => {
const id = crypto.randomUUID();
const now = Date.now();
getDb().insert(taskComments).values({
id,
taskId: input.taskId,
author: input.author,
content: input.content,
createdAt: now,
}).run();
return { id, taskId: input.taskId, author: input.author, content: input.content, createdAt: now };
}),
delete: publicProcedure
.input(z.object({ id: z.string() }))
.mutation(({ input }) => {
getDb().delete(taskComments).where(eq(taskComments.id, input.id)).run();
return { success: true as const };
}),
});
const settingsRouter = router({
getSidebarCollapsed: publicProcedure.query(() => getStore().get('sidebarCollapsed')),
setSidebarCollapsed: publicProcedure
@@ -458,8 +495,13 @@ const aiRouter = router({
.mutation(() => ({ response: '' })),
setToken: publicProcedure
.input(z.object({ token: z.string() }))
.mutation(() => null),
hasToken: publicProcedure.query(() => false),
.mutation(async ({ input }) => {
await saveTokenAndInit(input.token);
return { success: true };
}),
hasToken: publicProcedure.query(async () => {
return hasActiveToken();
}),
});
export const appRouter = router({
@@ -470,6 +512,7 @@ export const appRouter = router({
tasks: tasksRouter,
checkpoints: checkpointsRouter,
notes: notesRouter,
taskComments: taskCommentsRouter,
ai: aiRouter,
});