Add v0.1.3 UI features: metadata display, task menu, and search

- Display time estimates, tags, and flag colors in TreeView and KanbanView
- Add TaskMenu component with three-dot dropdown for editing metadata
  - Edit time estimates (stored as minutes)
  - Edit tags (comma-separated input)
  - Set flag colors (red, orange, yellow, green, blue, purple, pink)
- Add SearchBar component in header
  - Real-time search with 300ms debounce
  - Optional project filtering
  - Click results to navigate to project
- Integrate TaskMenu into both TreeView and KanbanView
- Format time display: "30m" for <60 min, "1.5h" for >=60 min
This commit is contained in:
Claude
2025-11-20 08:23:07 +00:00
parent 70b64d276f
commit b395ee8103
5 changed files with 636 additions and 35 deletions

View File

@@ -3,10 +3,9 @@ import {
ChevronDown,
ChevronRight,
Plus,
Edit2,
Trash2,
Check,
X
X,
Flag
} from 'lucide-react'
import {
getProjectTaskTree,
@@ -14,6 +13,8 @@ import {
updateTask,
deleteTask
} from '../utils/api'
import { formatTime } from '../utils/format'
import TaskMenu from './TaskMenu'
const STATUS_COLORS = {
backlog: 'text-gray-400',
@@ -29,6 +30,16 @@ const STATUS_LABELS = {
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)
@@ -139,10 +150,43 @@ function TaskNode({ task, projectId, onUpdate, level = 0 }) {
) : (
<>
<div className="flex-1">
<span className="text-gray-200">{task.title}</span>
<span className={`ml-3 text-xs ${STATUS_COLORS[task.status]}`}>
{STATUS_LABELS[task.status]}
</span>
<div className="flex items-center gap-2">
{/* Flag indicator */}
{task.flag_color && FLAG_COLORS[task.flag_color] && (
<Flag size={14} className={`${FLAG_COLORS[task.flag_color].replace('bg-', 'text-')}`} fill="currentColor" />
)}
<span className="text-gray-200">{task.title}</span>
<span className={`ml-2 text-xs ${STATUS_COLORS[task.status]}`}>
{STATUS_LABELS[task.status]}
</span>
</div>
{/* Metadata row */}
{(task.estimated_minutes || (task.tags && task.tags.length > 0)) && (
<div className="flex items-center gap-3 mt-1">
{/* Time estimate */}
{task.estimated_minutes && (
<div className="flex items-center gap-1 text-xs text-gray-500">
<Clock size={12} />
<span>{formatTime(task.estimated_minutes)}</span>
</div>
)}
{/* Tags */}
{task.tags && task.tags.length > 0 && (
<div className="flex items-center gap-1 flex-wrap">
{task.tags.map((tag, idx) => (
<span
key={idx}
className="inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-cyber-orange/20 text-cyber-orange border border-cyber-orange/30 rounded"
>
{tag}
</span>
))}
</div>
)}
</div>
)}
</div>
{/* Actions */}
@@ -154,20 +198,12 @@ function TaskNode({ task, projectId, onUpdate, level = 0 }) {
>
<Plus size={16} />
</button>
<button
onClick={() => setIsEditing(true)}
className="text-gray-400 hover:text-gray-200"
title="Edit"
>
<Edit2 size={16} />
</button>
<button
onClick={handleDelete}
className="text-gray-600 hover:text-red-400"
title="Delete"
>
<Trash2 size={16} />
</button>
<TaskMenu
task={task}
onUpdate={onUpdate}
onDelete={handleDelete}
onEdit={() => setIsEditing(true)}
/>
</div>
</>
)}