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