diff --git a/src/components/KanbanBoard.tsx b/src/components/KanbanBoard.tsx index 5d06361..61e43ed 100644 --- a/src/components/KanbanBoard.tsx +++ b/src/components/KanbanBoard.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from "react"; import { Task, getTasks, updateTask } from "@/lib/api"; import TaskModal from "@/components/TaskModal"; import CreateTaskModal from "@/components/CreateTaskModal"; +import { wsClient } from "@/lib/ws"; const COLUMNS = [ { key: "backlog", label: "Backlog", color: "#737373" }, @@ -44,7 +45,34 @@ export default function KanbanBoard({ projectId, projectSlug }: Props) { } }; - useEffect(() => { loadTasks(); }, [projectId]); + useEffect(() => { + loadTasks(); + + // Subscribe to WebSocket events for real-time updates + const unsubscribeCreated = wsClient.on("task.created", (data: any) => { + if (data.project_id === projectId) { + setTasks((prev) => [...prev, data as Task]); + } + }); + + const unsubscribeUpdated = wsClient.on("task.updated", (data: any) => { + if (data.project_id === projectId) { + setTasks((prev) => prev.map((t) => t.id === data.id ? { ...t, ...data } : t)); + } + }); + + const unsubscribeAssigned = wsClient.on("task.assigned", (data: any) => { + if (data.project_id === projectId) { + setTasks((prev) => prev.map((t) => t.id === data.id ? { ...t, assignee_slug: data.assignee_slug, assigned_at: data.assigned_at } : t)); + } + }); + + return () => { + unsubscribeCreated?.(); + unsubscribeUpdated?.(); + unsubscribeAssigned?.(); + }; + }, [projectId]); const handleDrop = async (status: string) => { if (!draggedTask) return; diff --git a/src/lib/api.ts b/src/lib/api.ts index 9d60654..2df4411 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -173,6 +173,32 @@ export async function deleteTask(taskId: string): Promise { await request(`/api/v1/tasks/${taskId}`, { method: "DELETE" }); } +export async function takeTask(taskId: string, slug: string): Promise { + return request(`/api/v1/tasks/${taskId}/take?slug=${slug}`, { method: "POST" }); +} + +export async function rejectTask(taskId: string, reason: string): Promise<{ok: boolean; reason: string; old_assignee: string}> { + return request(`/api/v1/tasks/${taskId}/reject`, { + method: "POST", + body: JSON.stringify({ reason }) + }); +} + +export async function assignTask(taskId: string, assigneeSlug: string): Promise { + return request(`/api/v1/tasks/${taskId}/assign`, { + method: "POST", + body: JSON.stringify({ assignee_slug: assigneeSlug }) + }); +} + +export async function watchTask(taskId: string, slug: string): Promise<{ok: boolean; watchers: string[]}> { + return request(`/api/v1/tasks/${taskId}/watch?slug=${slug}`, { method: "POST" }); +} + +export async function unwatchTask(taskId: string, slug: string): Promise<{ok: boolean; watchers: string[]}> { + return request(`/api/v1/tasks/${taskId}/watch?slug=${slug}`, { method: "DELETE" }); +} + // --- Steps --- export async function getSteps(taskId: string): Promise {