diff --git a/client/pages/ProjectBoard.tsx b/client/pages/ProjectBoard.tsx new file mode 100644 index 00000000..085b7c9d --- /dev/null +++ b/client/pages/ProjectBoard.tsx @@ -0,0 +1,142 @@ +import Layout from "@/components/Layout"; +import { useAuth } from "@/contexts/AuthContext"; +import { useEffect, useMemo, useState } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Badge } from "@/components/ui/badge"; +import { aethexCollabService } from "@/lib/aethex-collab-service"; +import LoadingScreen from "@/components/LoadingScreen"; + +const columns: { key: "todo"|"doing"|"done"|"blocked"; title: string; hint: string }[] = [ + { key: "todo", title: "To do", hint: "Planned" }, + { key: "doing", title: "In progress", hint: "Active" }, + { key: "done", title: "Done", hint: "Completed" }, + { key: "blocked", title: "Blocked", hint: "Needs attention" }, +]; + +export default function ProjectBoard() { + const { user, loading } = useAuth(); + const navigate = useNavigate(); + const { projectId } = useParams<{ projectId: string }>(); + const [isLoading, setIsLoading] = useState(true); + const [tasks, setTasks] = useState([]); + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + const [creating, setCreating] = useState(false); + + useEffect(() => { + if (!loading && !user) navigate("/login", { replace: true }); + }, [loading, user, navigate]); + + const load = async () => { + if (!projectId) return; + setIsLoading(true); + try { + const rows = await aethexCollabService.listProjectTasks(projectId); + setTasks(rows); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + load(); + }, [projectId]); + + const grouped = useMemo(() => { + const map: Record = { todo: [], doing: [], done: [], blocked: [] }; + for (const t of tasks) { + map[t.status || "todo"].push(t); + } + return map; + }, [tasks]); + + const handleCreate = async () => { + if (!user?.id || !projectId) return; + if (!title.trim()) return; + setCreating(true); + try { + await aethexCollabService.createTask(projectId, title.trim(), description.trim() || null, null, null); + setTitle(""); + setDescription(""); + await load(); + } finally { + setCreating(false); + } + }; + + const move = async (taskId: string, status: "todo"|"doing"|"done"|"blocked") => { + await aethexCollabService.updateTaskStatus(taskId, status); + await load(); + }; + + if (loading || isLoading) return ; + if (!user) return null; + + return ( + +
+
+
+

Project Board

+

Track tasks by status. Drag-and-drop coming next.

+
+ +
+ {columns.map((col) => ( + + + + {col.title} + {grouped[col.key].length} + + {col.hint} + + + {grouped[col.key].length === 0 ? ( +

No tasks.

+ ) : ( + grouped[col.key].map((t) => ( +
+
{t.title}
+ {t.description ? ( +

{t.description}

+ ) : null} +
+ {columns.map((k) => ( + + ))} +
+
+ )) + )} +
+
+ ))} +
+ + + + Add task + Keep titles concise; details optional. + + + setTitle(e.target.value)} /> +