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' import TaskForm from './TaskForm' // Helper to format status label const formatStatusLabel = (status) => { return status.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ') } // Helper to get status color const getStatusColor = (status) => { const lowerStatus = status.toLowerCase() if (lowerStatus === 'backlog') return 'text-gray-400' if (lowerStatus === 'in_progress' || lowerStatus.includes('progress')) return 'text-blue-400' if (lowerStatus === 'on_hold' || lowerStatus.includes('hold') || lowerStatus.includes('waiting')) return 'text-yellow-400' if (lowerStatus === 'done' || lowerStatus.includes('complete')) return 'text-green-400' if (lowerStatus.includes('blocked')) return 'text-red-400' return 'text-purple-400' // default for custom statuses } 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, projectStatuses }) { 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 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 (taskData) => { try { await createTask({ project_id: parseInt(projectId), parent_task_id: task.id, title: taskData.title, description: taskData.description, status: taskData.status, tags: taskData.tags, estimated_minutes: taskData.estimated_minutes, flag_color: taskData.flag_color }) 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} {formatStatusLabel(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} ))}
)}
)} {/* Description */} {task.description && (
{task.description}
)}
{/* Actions */}
setIsEditing(true)} projectStatuses={projectStatuses} />
)}
{/* Add Subtask Form */} {showAddSubtask && (
setShowAddSubtask(false)} submitLabel="Add Subtask" projectStatuses={projectStatuses} />
)} {/* Subtasks */} {isExpanded && hasSubtasks && (
{task.subtasks.map(subtask => ( ))}
)}
) } function TreeView({ projectId, project }) { const projectStatuses = project?.statuses || ['backlog', 'in_progress', 'on_hold', 'done'] const [tasks, setTasks] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [showAddRoot, setShowAddRoot] = useState(false) 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 (taskData) => { try { await createTask({ project_id: parseInt(projectId), parent_task_id: null, title: taskData.title, description: taskData.description, status: taskData.status, tags: taskData.tags, estimated_minutes: taskData.estimated_minutes, flag_color: taskData.flag_color }) setShowAddRoot(false) loadTasks() } catch (err) { alert(`Error: ${err.message}`) } } if (loading) { return
Loading tasks...
} if (error) { return
{error}
} return (

Task Tree

{showAddRoot && (
setShowAddRoot(false)} submitLabel="Add Task" projectStatuses={projectStatuses} />
)} {tasks.length === 0 ? (

No tasks yet

Add a root task to get started

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