Fix tree view indentation and kanban subtask handling
Tree View: - Fix nested task indentation to scale properly with depth (1.5rem per level) - Sub-subtasks now indent correctly relative to their parents Kanban View: - Show ALL tasks as draggable cards (not just root tasks) - Subtasks display parent task name for context - Each subtask can be independently dragged to different status columns - Remove nested subtask expansion since all tasks are now individual cards
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { Plus, Edit2, Trash2, Check, X, ChevronDown, ChevronRight } from 'lucide-react'
|
import { Plus, Edit2, Trash2, Check, X } from 'lucide-react'
|
||||||
import {
|
import {
|
||||||
getProjectTasks,
|
getProjectTasks,
|
||||||
createTask,
|
createTask,
|
||||||
@@ -17,10 +17,11 @@ const STATUSES = [
|
|||||||
function TaskCard({ task, allTasks, onUpdate, onDragStart }) {
|
function TaskCard({ task, allTasks, onUpdate, onDragStart }) {
|
||||||
const [isEditing, setIsEditing] = useState(false)
|
const [isEditing, setIsEditing] = useState(false)
|
||||||
const [editTitle, setEditTitle] = useState(task.title)
|
const [editTitle, setEditTitle] = useState(task.title)
|
||||||
const [showSubtasks, setShowSubtasks] = useState(false)
|
|
||||||
|
|
||||||
const subtasks = allTasks.filter(t => t.parent_task_id === task.id)
|
// Find parent task if this is a subtask
|
||||||
const hasSubtasks = subtasks.length > 0
|
const parentTask = task.parent_task_id
|
||||||
|
? allTasks.find(t => t.id === task.parent_task_id)
|
||||||
|
: null
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -75,8 +76,15 @@ function TaskCard({ task, allTasks, onUpdate, onDragStart }) {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="flex justify-between items-start mb-2">
|
<div className="flex justify-between items-start">
|
||||||
<span className="text-gray-200 text-sm flex-1">{task.title}</span>
|
<div className="flex-1">
|
||||||
|
<div className="text-gray-200 text-sm">{task.title}</div>
|
||||||
|
{parentTask && (
|
||||||
|
<div className="text-xs text-gray-500 mt-1">
|
||||||
|
↳ subtask of: <span className="text-cyber-orange">{parentTask.title}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsEditing(true)}
|
onClick={() => setIsEditing(true)}
|
||||||
@@ -92,27 +100,6 @@ function TaskCard({ task, allTasks, onUpdate, onDragStart }) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{hasSubtasks && (
|
|
||||||
<div className="mt-2 pt-2 border-t border-cyber-orange/20">
|
|
||||||
<button
|
|
||||||
onClick={() => setShowSubtasks(!showSubtasks)}
|
|
||||||
className="flex items-center gap-1 text-xs text-cyber-orange hover:text-cyber-orange-bright"
|
|
||||||
>
|
|
||||||
{showSubtasks ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
|
|
||||||
{subtasks.length} subtask{subtasks.length !== 1 ? 's' : ''}
|
|
||||||
</button>
|
|
||||||
{showSubtasks && (
|
|
||||||
<div className="mt-2 pl-3 space-y-1">
|
|
||||||
{subtasks.map(subtask => (
|
|
||||||
<div key={subtask.id} className="text-xs text-gray-400">
|
|
||||||
• {subtask.title}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -255,9 +242,6 @@ function KanbanView({ projectId }) {
|
|||||||
return <div className="text-center text-red-400 py-12">{error}</div>
|
return <div className="text-center text-red-400 py-12">{error}</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only show root-level tasks in Kanban (tasks without parents)
|
|
||||||
const rootTasks = allTasks.filter(t => !t.parent_task_id)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-semibold text-gray-300 mb-4">Kanban Board</h3>
|
<h3 className="text-xl font-semibold text-gray-300 mb-4">Kanban Board</h3>
|
||||||
@@ -267,7 +251,7 @@ function KanbanView({ projectId }) {
|
|||||||
<KanbanColumn
|
<KanbanColumn
|
||||||
key={status.key}
|
key={status.key}
|
||||||
status={status}
|
status={status}
|
||||||
tasks={rootTasks.filter(t => t.status === status.key)}
|
tasks={allTasks.filter(t => t.status === status.key)}
|
||||||
allTasks={allTasks}
|
allTasks={allTasks}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
onUpdate={loadTasks}
|
onUpdate={loadTasks}
|
||||||
@@ -277,7 +261,7 @@ function KanbanView({ projectId }) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{rootTasks.length === 0 && (
|
{allTasks.length === 0 && (
|
||||||
<div className="text-center py-16 text-gray-500">
|
<div className="text-center py-16 text-gray-500">
|
||||||
<p className="text-lg mb-2">No tasks yet</p>
|
<p className="text-lg mb-2">No tasks yet</p>
|
||||||
<p className="text-sm">Add tasks using the + button in any column</p>
|
<p className="text-sm">Add tasks using the + button in any column</p>
|
||||||
|
|||||||
@@ -85,9 +85,8 @@ function TaskNode({ task, projectId, onUpdate, level = 0 }) {
|
|||||||
return (
|
return (
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<div
|
<div
|
||||||
className={`flex items-center gap-2 p-3 bg-cyber-darkest border border-cyber-orange/20 rounded hover:border-cyber-orange/40 transition-all group ${
|
style={{ marginLeft: `${level * 1.5}rem` }}
|
||||||
level > 0 ? 'ml-6' : ''
|
className="flex items-center gap-2 p-3 bg-cyber-darkest border border-cyber-orange/20 rounded hover:border-cyber-orange/40 transition-all group"
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{/* Expand/Collapse */}
|
{/* Expand/Collapse */}
|
||||||
{hasSubtasks && (
|
{hasSubtasks && (
|
||||||
@@ -176,7 +175,7 @@ function TaskNode({ task, projectId, onUpdate, level = 0 }) {
|
|||||||
|
|
||||||
{/* Add Subtask Form */}
|
{/* Add Subtask Form */}
|
||||||
{showAddSubtask && (
|
{showAddSubtask && (
|
||||||
<div className={`mt-2 ${level > 0 ? 'ml-6' : ''}`}>
|
<div style={{ marginLeft: `${level * 1.5}rem` }} className="mt-2">
|
||||||
<form onSubmit={handleAddSubtask} className="flex gap-2">
|
<form onSubmit={handleAddSubtask} className="flex gap-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
Reference in New Issue
Block a user