feat: due dates, subtasks, and task detail page (HQ-{number} URLs)

- Schema: added due_date and subtasks JSONB columns to tasks
- API: CRUD endpoints for subtasks (/tasks/:id/subtasks)
- API: due date support in create/update task
- TaskDetailPanel: due date picker with overdue/soon badges
- TaskDetailPanel: subtask checklist with progress bar
- TaskPage: full-page task view at /task/HQ-{number}
- Dashboard: task cards link to detail page, show subtask progress & due date badges
- Migration: 0001_mighty_callisto.sql
This commit is contained in:
2026-01-29 07:06:59 +00:00
parent f2b477c03d
commit e874cafbec
11 changed files with 1433 additions and 5 deletions

View File

@@ -115,6 +115,38 @@ export async function deleteProject(id: string): Promise<void> {
if (!res.ok) throw new Error("Failed to delete project");
}
// Subtasks
export async function addSubtask(taskId: string, title: string): Promise<Task> {
const res = await fetch(`${BASE}/${taskId}/subtasks`, {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title }),
});
if (!res.ok) throw new Error("Failed to add subtask");
return res.json();
}
export async function toggleSubtask(taskId: string, subtaskId: string, completed: boolean): Promise<Task> {
const res = await fetch(`${BASE}/${taskId}/subtasks/${subtaskId}`, {
method: "PATCH",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ completed }),
});
if (!res.ok) throw new Error("Failed to toggle subtask");
return res.json();
}
export async function deleteSubtask(taskId: string, subtaskId: string): Promise<Task> {
const res = await fetch(`${BASE}/${taskId}/subtasks/${subtaskId}`, {
method: "DELETE",
credentials: "include",
});
if (!res.ok) throw new Error("Failed to delete subtask");
return res.json();
}
// Progress Notes
export async function addProgressNote(taskId: string, note: string): Promise<Task> {
const res = await fetch(`${BASE}/${taskId}/notes`, {