feat: add personal todos feature

- New todos table in DB schema (title, description, priority, category, due date, completion)
- Full CRUD + toggle API routes at /api/todos
- Categories support with filtering
- Bulk import endpoint for migration
- New TodosPage with inline editing, priority badges, due date display
- Add Todos to sidebar navigation
- Dark mode support throughout
This commit is contained in:
2026-01-30 04:42:34 +00:00
parent d5693a7624
commit dd2c80224e
9 changed files with 902 additions and 1 deletions

View File

@@ -1,4 +1,4 @@
import type { Task, Project, ProjectWithTasks, VelocityStats, Recurrence } from "./types";
import type { Task, Project, ProjectWithTasks, VelocityStats, Recurrence, Todo, TodoPriority } from "./types";
const BASE = "/api/tasks";
@@ -228,3 +228,75 @@ export async function deleteUser(userId: string): Promise<void> {
});
if (!res.ok) throw new Error("Failed to delete user");
}
// ─── Todos API ───
const TODOS_BASE = "/api/todos";
export async function fetchTodos(params?: { completed?: string; category?: string }): Promise<Todo[]> {
const url = new URL(TODOS_BASE, window.location.origin);
if (params?.completed) url.searchParams.set("completed", params.completed);
if (params?.category) url.searchParams.set("category", params.category);
const res = await fetch(url.toString(), { credentials: "include" });
if (!res.ok) throw new Error(res.status === 401 ? "Unauthorized" : "Failed to fetch todos");
return res.json();
}
export async function fetchTodoCategories(): Promise<string[]> {
const res = await fetch(`${TODOS_BASE}/categories`, { credentials: "include" });
if (!res.ok) throw new Error("Failed to fetch categories");
return res.json();
}
export async function createTodo(todo: {
title: string;
description?: string;
priority?: TodoPriority;
category?: string;
dueDate?: string | null;
}): Promise<Todo> {
const res = await fetch(TODOS_BASE, {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(todo),
});
if (!res.ok) throw new Error("Failed to create todo");
return res.json();
}
export async function updateTodo(id: string, updates: Partial<{
title: string;
description: string;
priority: TodoPriority;
category: string | null;
dueDate: string | null;
isCompleted: boolean;
sortOrder: number;
}>): Promise<Todo> {
const res = await fetch(`${TODOS_BASE}/${id}`, {
method: "PATCH",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(updates),
});
if (!res.ok) throw new Error("Failed to update todo");
return res.json();
}
export async function toggleTodo(id: string): Promise<Todo> {
const res = await fetch(`${TODOS_BASE}/${id}/toggle`, {
method: "PATCH",
credentials: "include",
});
if (!res.ok) throw new Error("Failed to toggle todo");
return res.json();
}
export async function deleteTodo(id: string): Promise<void> {
const res = await fetch(`${TODOS_BASE}/${id}`, {
method: "DELETE",
credentials: "include",
});
if (!res.ok) throw new Error("Failed to delete todo");
}