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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; 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); const [members, setMembers] = useState([]); const [assigneeId, setAssigneeId] = useState(""); const [dueDate, setDueDate] = useState(""); const [q, setQ] = useState(""); const [filterAssignee, setFilterAssignee] = useState(""); const [filterStatus, setFilterStatus] = useState(""); 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); const m = await aethexCollabService.listProjectMembers(projectId); setMembers(m); } finally { setIsLoading(false); } }; useEffect(() => { load(); }, [projectId]); const grouped = useMemo(() => { const map: Record = { todo: [], doing: [], done: [], blocked: [], }; const normalized = tasks.filter((t) => { if (q && !String(t.title || "").toLowerCase().includes(q.toLowerCase()) && !String(t.description || "").toLowerCase().includes(q.toLowerCase())) { return false; } if (filterAssignee && String(t.assignee_id || "") !== filterAssignee) return false; if (filterStatus && String(t.status || "") !== filterStatus) return false; return true; }); for (const t of normalized) { map[t.status || "todo"].push(t); } return map; }, [tasks, q, filterAssignee, filterStatus]); const handleCreate = async () => { if (!user?.id || !projectId) return; if (!title.trim()) return; setCreating(true); try { await aethexCollabService.createTask( projectId, title.trim(), description.trim() || null, assigneeId || null, dueDate || null, ); setTitle(""); setDescription(""); setAssigneeId(""); setDueDate(""); 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. Filters, assignees, and due dates enabled.

setQ(e.target.value)} />
{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}
{t.assignee ? ( {t.assignee.full_name || t.assignee.username || "Assignee"} ) : ( Unassigned )} {t.due_date ? ( • Due {new Date(t.due_date).toLocaleDateString()} ) : null}
{columns.map((k) => ( ))}
)) )}
))}
Add task Keep titles concise; details optional. setTitle(e.target.value)} />