import { useState, useEffect } from 'react' import { ChevronDown, ChevronRight, Plus, Check, X, Flag, Clock } from 'lucide-react' import { getProjectTaskTree, createTask, updateTask, deleteTask } from '../utils/api' import { formatTimeWithTotal } from '../utils/format' import TaskMenu from './TaskMenu' const STATUS_COLORS = { backlog: 'text-gray-400', in_progress: 'text-blue-400', blocked: 'text-red-400', done: 'text-green-400' } const STATUS_LABELS = { backlog: 'Backlog', in_progress: 'In Progress', blocked: 'Blocked', done: 'Done' } const FLAG_COLORS = { red: 'bg-red-500', orange: 'bg-orange-500', yellow: 'bg-yellow-500', green: 'bg-green-500', blue: 'bg-blue-500', purple: 'bg-purple-500', pink: 'bg-pink-500' } function TaskNode({ task, projectId, onUpdate, level = 0 }) { const [isExpanded, setIsExpanded] = useState(true) const [isEditing, setIsEditing] = useState(false) const [editTitle, setEditTitle] = useState(task.title) const [editStatus, setEditStatus] = useState(task.status) const [showAddSubtask, setShowAddSubtask] = useState(false) const [newSubtaskTitle, setNewSubtaskTitle] = useState('') const hasSubtasks = task.subtasks && task.subtasks.length > 0 const handleSave = async () => { try { await updateTask(task.id, { title: editTitle, status: editStatus }) setIsEditing(false) onUpdate() } catch (err) { alert(`Error: ${err.message}`) } } const handleDelete = async () => { if (!confirm('Delete this task and all its subtasks?')) return try { await deleteTask(task.id) onUpdate() } catch (err) { alert(`Error: ${err.message}`) } } const handleAddSubtask = async (e) => { e.preventDefault() if (!newSubtaskTitle.trim()) return try { await createTask({ project_id: parseInt(projectId), parent_task_id: task.id, title: newSubtaskTitle, status: 'backlog' }) setNewSubtaskTitle('') setShowAddSubtask(false) setIsExpanded(true) onUpdate() } catch (err) { alert(`Error: ${err.message}`) } } return (
{/* Expand/Collapse */} {hasSubtasks && ( )} {!hasSubtasks &&
} {/* Task Content */} {isEditing ? (
setEditTitle(e.target.value)} className="flex-1 px-2 py-1 bg-cyber-darker border border-cyber-orange/50 rounded text-gray-100 text-sm focus:outline-none focus:border-cyber-orange" autoFocus />
) : ( <>
{/* Flag indicator */} {task.flag_color && FLAG_COLORS[task.flag_color] && ( )} {task.title} {STATUS_LABELS[task.status]}
{/* Metadata row */} {(formatTimeWithTotal(task) || (task.tags && task.tags.length > 0)) && (
{/* Time estimate */} {formatTimeWithTotal(task) && (
{formatTimeWithTotal(task)}
)} {/* Tags */} {task.tags && task.tags.length > 0 && (
{task.tags.map((tag, idx) => ( {tag} ))}
)}
)}
{/* Actions */}
setIsEditing(true)} />
)}
{/* Add Subtask Form */} {showAddSubtask && (
setNewSubtaskTitle(e.target.value)} placeholder="New subtask title..." className="flex-1 px-3 py-2 bg-cyber-darker border border-cyber-orange/50 rounded text-gray-100 text-sm focus:outline-none focus:border-cyber-orange" autoFocus />
)} {/* Subtasks */} {isExpanded && hasSubtasks && (
{task.subtasks.map(subtask => ( ))}
)}
) } function TreeView({ projectId }) { const [tasks, setTasks] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [showAddRoot, setShowAddRoot] = useState(false) const [newTaskTitle, setNewTaskTitle] = useState('') useEffect(() => { loadTasks() }, [projectId]) const loadTasks = async () => { try { setLoading(true) const data = await getProjectTaskTree(projectId) setTasks(data) } catch (err) { setError(err.message) } finally { setLoading(false) } } const handleAddRootTask = async (e) => { e.preventDefault() if (!newTaskTitle.trim()) return try { await createTask({ project_id: parseInt(projectId), parent_task_id: null, title: newTaskTitle, status: 'backlog' }) setNewTaskTitle('') setShowAddRoot(false) loadTasks() } catch (err) { alert(`Error: ${err.message}`) } } if (loading) { return
Loading tasks...
} if (error) { return
{error}
} return (

Task Tree

{showAddRoot && (
setNewTaskTitle(e.target.value)} placeholder="New task title..." className="flex-1 px-3 py-2 bg-cyber-darker border border-cyber-orange/50 rounded text-gray-100 focus:outline-none focus:border-cyber-orange" autoFocus />
)} {tasks.length === 0 ? (

No tasks yet

Add a root task to get started

) : (
{tasks.map(task => ( ))}
)}
) } export default TreeView