diff --git a/frontend/src/components/KanbanView.jsx b/frontend/src/components/KanbanView.jsx index 0f85aa2..e9c1ffe 100644 --- a/frontend/src/components/KanbanView.jsx +++ b/frontend/src/components/KanbanView.jsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react' -import { Plus, Check, X, Flag, Clock, ChevronDown, ChevronRight } from 'lucide-react' +import { Plus, Check, X, Flag, Clock, ChevronDown, ChevronRight, ChevronsDown, ChevronsUp } from 'lucide-react' import { getProjectTasks, createTask, @@ -27,6 +27,18 @@ const FLAG_COLORS = { pink: 'bg-pink-500' } +// Helper function to get all descendant tasks recursively +function getAllDescendants(taskId, allTasks) { + const children = allTasks.filter(t => t.parent_task_id === taskId) + let descendants = [...children] + + for (const child of children) { + descendants = descendants.concat(getAllDescendants(child.id, allTasks)) + } + + return descendants +} + // Helper function to get all descendant tasks of a parent in a specific status function getDescendantsInStatus(taskId, allTasks, status) { const children = allTasks.filter(t => t.parent_task_id === taskId) @@ -48,11 +60,19 @@ function hasDescendantsInStatus(taskId, allTasks, status) { return getDescendantsInStatus(taskId, allTasks, status).length > 0 } -function TaskCard({ task, allTasks, onUpdate, onDragStart, isParent, columnStatus }) { +function TaskCard({ task, allTasks, onUpdate, onDragStart, isParent, columnStatus, expandedCards, setExpandedCards }) { const [isEditing, setIsEditing] = useState(false) - const [isExpanded, setIsExpanded] = useState(false) const [editTitle, setEditTitle] = useState(task.title) + // Use global expanded state + const isExpanded = expandedCards[task.id] || false + const toggleExpanded = () => { + setExpandedCards(prev => ({ + ...prev, + [task.id]: !prev[task.id] + })) + } + const handleSave = async () => { try { await updateTask(task.id, { title: editTitle }) @@ -80,13 +100,13 @@ function TaskCard({ task, allTasks, onUpdate, onDragStart, isParent, columnStatu return (
!isParent && onDragStart(e, task)} + draggable={!isEditing} + onDragStart={(e) => onDragStart(e, task, isParent)} className={`${ isParent ? 'bg-cyber-darker border-2 border-cyber-orange/50' : 'bg-cyber-darkest border border-cyber-orange/30' - } rounded-lg p-3 ${!isParent ? 'cursor-move' : ''} hover:border-cyber-orange/60 transition-all group`} + } rounded-lg p-3 cursor-move hover:border-cyber-orange/60 transition-all group`} > {isEditing ? (
@@ -121,7 +141,7 @@ function TaskCard({ task, allTasks, onUpdate, onDragStart, isParent, columnStatu {/* Expand/collapse for parent cards */} {isParent && childrenInColumn.length > 0 && (
)} + + {/* Description */} + {task.description && ( +
+ {task.description} +
+ )}
@@ -201,6 +228,8 @@ function TaskCard({ task, allTasks, onUpdate, onDragStart, isParent, columnStatu onDragStart={onDragStart} isParent={false} columnStatus={columnStatus} + expandedCards={expandedCards} + setExpandedCards={setExpandedCards} /> ))} @@ -209,7 +238,7 @@ function TaskCard({ task, allTasks, onUpdate, onDragStart, isParent, columnStatu ) } -function KanbanColumn({ status, allTasks, projectId, onUpdate, onDrop, onDragOver }) { +function KanbanColumn({ status, allTasks, projectId, onUpdate, onDrop, onDragOver, expandedCards, setExpandedCards }) { const [showAddTask, setShowAddTask] = useState(false) const handleAddTask = async (taskData) => { @@ -218,6 +247,7 @@ function KanbanColumn({ status, allTasks, projectId, onUpdate, onDrop, onDragOve project_id: parseInt(projectId), parent_task_id: null, title: taskData.title, + description: taskData.description, status: status.key, tags: taskData.tags, estimated_minutes: taskData.estimated_minutes, @@ -289,11 +319,14 @@ function KanbanColumn({ status, allTasks, projectId, onUpdate, onDrop, onDragOve task={task} allTasks={allTasks} onUpdate={onUpdate} - onDragStart={(e, task) => { + onDragStart={(e, task, isParent) => { e.dataTransfer.setData('taskId', task.id.toString()) + e.dataTransfer.setData('isParent', isParent.toString()) }} isParent={isParent} columnStatus={status.key} + expandedCards={expandedCards} + setExpandedCards={setExpandedCards} /> ) })} @@ -306,6 +339,7 @@ function KanbanView({ projectId }) { const [allTasks, setAllTasks] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState('') + const [expandedCards, setExpandedCards] = useState({}) useEffect(() => { loadTasks() @@ -323,6 +357,19 @@ function KanbanView({ projectId }) { } } + const handleExpandAll = () => { + const parentTasks = allTasks.filter(t => allTasks.some(child => child.parent_task_id === t.id)) + const newExpandedState = {} + parentTasks.forEach(task => { + newExpandedState[task.id] = true + }) + setExpandedCards(newExpandedState) + } + + const handleCollapseAll = () => { + setExpandedCards({}) + } + const handleDragOver = (e) => { e.preventDefault() } @@ -330,11 +377,22 @@ function KanbanView({ projectId }) { const handleDrop = async (e, newStatus) => { e.preventDefault() const taskId = parseInt(e.dataTransfer.getData('taskId')) + const isParent = e.dataTransfer.getData('isParent') === 'true' if (!taskId) return try { + // Update the dragged task await updateTask(taskId, { status: newStatus }) + + // If it's a parent task, update all descendants + if (isParent) { + const descendants = getAllDescendants(taskId, allTasks) + for (const descendant of descendants) { + await updateTask(descendant.id, { status: newStatus }) + } + } + loadTasks() } catch (err) { alert(`Error: ${err.message}`) @@ -351,7 +409,25 @@ function KanbanView({ projectId }) { return (
-

Kanban Board (Nested View)

+
+

Kanban Board (Nested View)

+
+ + +
+
{STATUSES.map(status => ( @@ -363,6 +439,8 @@ function KanbanView({ projectId }) { onUpdate={loadTasks} onDrop={handleDrop} onDragOver={handleDragOver} + expandedCards={expandedCards} + setExpandedCards={setExpandedCards} /> ))}
diff --git a/frontend/src/components/TaskForm.jsx b/frontend/src/components/TaskForm.jsx index 8a85ae4..049d893 100644 --- a/frontend/src/components/TaskForm.jsx +++ b/frontend/src/components/TaskForm.jsx @@ -14,6 +14,7 @@ const FLAG_COLORS = [ function TaskForm({ onSubmit, onCancel, submitLabel = "Add" }) { const [title, setTitle] = useState('') + const [description, setDescription] = useState('') const [tags, setTags] = useState('') const [hours, setHours] = useState('') const [minutes, setMinutes] = useState('') @@ -33,6 +34,7 @@ function TaskForm({ onSubmit, onCancel, submitLabel = "Add" }) { const taskData = { title: title.trim(), + description: description.trim() || null, tags: tagList && tagList.length > 0 ? tagList : null, estimated_minutes: totalMinutes > 0 ? totalMinutes : null, flag_color: flagColor @@ -56,6 +58,18 @@ function TaskForm({ onSubmit, onCancel, submitLabel = "Add" }) { />
+ {/* Description */} +
+ +