diff --git a/src/components/BacklogView.tsx b/src/components/BacklogView.tsx index 3eaf766..44625a4 100644 --- a/src/components/BacklogView.tsx +++ b/src/components/BacklogView.tsx @@ -18,10 +18,11 @@ import { } from "@dnd-kit/sortable" import { CSS } from "@dnd-kit/utilities" import { useTaskStore, Task, Sprint } from "@/stores/useTaskStore" -import { Card, CardContent } from "@/components/ui/card" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" -import { Plus, GripVertical, Search, Filter } from "lucide-react" +import { Plus, GripVertical, ChevronDown, ChevronRight, Calendar } from "lucide-react" +import { format } from "date-fns" const priorityColors: Record = { low: "bg-slate-600", @@ -31,32 +32,20 @@ const priorityColors: Record = { } const typeLabels: Record = { - idea: "💡 Idea", - task: "📋 Task", - bug: "🐛 Bug", - research: "🔬 Research", - plan: "📐 Plan", + idea: "💡", + task: "📋", + bug: "🐛", + research: "🔬", + plan: "📐", } -const typeColors: Record = { - idea: "bg-purple-500", - task: "bg-blue-500", - bug: "bg-red-500", - research: "bg-green-500", - plan: "bg-amber-500", -} - -// Sortable Backlog Item -function SortableBacklogItem({ +// Sortable Task Row +function SortableTaskRow({ task, - sprints, onClick, - onAssignToSprint, }: { task: Task - sprints: Sprint[] onClick: () => void - onAssignToSprint: (sprintId: string) => void }) { const { attributes, @@ -73,105 +62,117 @@ function SortableBacklogItem({ opacity: isDragging ? 0.5 : 1, } - const currentSprint = sprints.find((s) => s.id === task.sprintId) - return ( - - -
-
- -
- -
-
- - {typeLabels[task.type]} - - - {task.priority} - - {task.comments.length > 0 && ( - - 💬 {task.comments.length} - - )} -
-

- {task.title} -

-
- -
- {currentSprint ? ( - - {currentSprint.name} - - ) : ( - - )} -
-
-
-
+
+ +
+ {typeLabels[task.type]} +
+

{task.title}

+
+ + {task.priority} + + {task.comments.length > 0 && ( + 💬 {task.comments.length} + )} + ) } // Drag Overlay Item function DragOverlayItem({ task }: { task: Task }) { return ( - - +
+ + {typeLabels[task.type]} +
+

{task.title}

+
+ + {task.priority} + +
+ ) +} + +// Collapsible Section +function TaskSection({ + title, + tasks, + isOpen, + onToggle, + onTaskClick, + sprintInfo, +}: { + title: string + tasks: Task[] + isOpen: boolean + onToggle: () => void + onTaskClick: (task: Task) => void + sprintInfo?: { name: string; date: string; status: string } +}) { + return ( +
+ + + {isOpen && ( +
+ t.id)} + strategy={verticalListSortingStrategy} + > +
+ {tasks.length === 0 ? ( +

No tasks

+ ) : ( + tasks.map((task) => ( + onTaskClick(task)} + /> + )) + )} +
+
+
+ )} +
) } @@ -180,17 +181,24 @@ export function BacklogView() { tasks, sprints, selectedProjectId, - selectedTaskId, - updateTask, selectTask, - addTask, + updateTask, + addSprint, } = useTaskStore() - const [searchQuery, setSearchQuery] = useState("") - const [filterType, setFilterType] = useState("all") - const [filterPriority, setFilterPriority] = useState("all") const [activeId, setActiveId] = useState(null) - const [isCreatingTask, setIsCreatingTask] = useState(false) + const [openSections, setOpenSections] = useState>({ + current: true, + other: false, + backlog: true, + }) + const [isCreatingSprint, setIsCreatingSprint] = useState(false) + const [newSprint, setNewSprint] = useState({ + name: "", + goal: "", + startDate: "", + endDate: "", + }) // Sensors for drag detection const sensors = useSensors( @@ -201,33 +209,28 @@ export function BacklogView() { }) ) - // Get backlog tasks (backlog status, no sprint assigned) - const backlogTasks = tasks.filter( - (t) => - t.projectId === selectedProjectId && - t.status === "backlog" && - !t.sprintId + // Get current active sprint + const now = new Date() + const currentSprint = sprints.find( + (s) => + s.status === "active" && + new Date(s.startDate) <= now && + new Date(s.endDate) >= now ) - // Filter tasks - const filteredTasks = backlogTasks.filter((task) => { - const matchesSearch = task.title - .toLowerCase() - .includes(searchQuery.toLowerCase()) - const matchesType = filterType === "all" || task.type === filterType - const matchesPriority = - filterPriority === "all" || task.priority === filterPriority - return matchesSearch && matchesType && matchesPriority - }) + // Get other sprints (not current) + const otherSprints = sprints.filter((s) => s.id !== currentSprint?.id) - // Sort by priority (urgent > high > medium > low) - const priorityOrder = { urgent: 0, high: 1, medium: 2, low: 3 } - const sortedTasks = [...filteredTasks].sort( - (a, b) => priorityOrder[a.priority] - priorityOrder[b.priority] + // Get tasks by section + const currentSprintTasks = currentSprint + ? tasks.filter((t) => t.sprintId === currentSprint.id) + : [] + + const otherSprintsTasks = otherSprints.flatMap((sprint) => + tasks.filter((t) => t.sprintId === sprint.id) ) - // Get project sprints - const projectSprints = sprints.filter((s) => s.projectId === selectedProjectId) + const backlogTasks = tasks.filter((t) => !t.sprintId) // Get active task for drag overlay const activeTask = activeId ? tasks.find((t) => t.id === activeId) : null @@ -242,27 +245,38 @@ export function BacklogView() { if (!over) return - // Reordering logic would go here - // For now, just a placeholder for future implementation + const taskId = active.id as string + const overId = over.id as string + + // If dropped over a section header, move task to that section's sprint + if (overId === "backlog") { + updateTask(taskId, { sprintId: undefined }) + } else if (overId === "current" && currentSprint) { + updateTask(taskId, { sprintId: currentSprint.id }) + } else if (overId.startsWith("sprint-")) { + const sprintId = overId.replace("sprint-", "") + updateTask(taskId, { sprintId }) + } } - const handleAssignToSprint = (taskId: string, sprintId: string) => { - updateTask(taskId, { sprintId }) + const toggleSection = (section: string) => { + setOpenSections((prev) => ({ ...prev, [section]: !prev[section] })) } - const handleCreateTask = () => { - if (!selectedProjectId) return + const handleCreateSprint = () => { + if (!newSprint.name) return - addTask({ - title: "New Backlog Item", - description: "", - type: "task", - priority: "medium", - status: "backlog", - projectId: selectedProjectId, - tags: [], + addSprint({ + name: newSprint.name, + goal: newSprint.goal, + startDate: newSprint.startDate || new Date().toISOString(), + endDate: newSprint.endDate || new Date().toISOString(), + status: "planning", + projectId: selectedProjectId || "2", }) - setIsCreatingTask(false) + + setIsCreatingSprint(false) + setNewSprint({ name: "", goal: "", startDate: "", endDate: "" }) } return ( @@ -273,161 +287,106 @@ export function BacklogView() { onDragEnd={handleDragEnd} >
- {/* Header */} -
-
-
- + {/* Current Sprint Section */} +
+ toggleSection("current")} + onTaskClick={(task) => selectTask(task.id)} + sprintInfo={ + currentSprint + ? { + name: currentSprint.name, + date: `${format(new Date(currentSprint.startDate), "MMM d")} - ${format( + new Date(currentSprint.endDate), + "MMM d" + )}`, + status: currentSprint.status, + } + : undefined + } + /> +
+ + {/* Other Sprints Section */} + {otherSprints.length > 0 && ( +
+ toggleSection("other")} + onTaskClick={(task) => selectTask(task.id)} + /> +
+ )} + + {/* Create Sprint Button */} + {!isCreatingSprint ? ( + + ) : ( + + +

Create New Sprint

setSearchQuery(e.target.value)} - className="pl-9 pr-4 py-2 bg-slate-800 border border-slate-700 rounded-lg text-sm text-slate-200 focus:outline-none focus:border-blue-500 w-full sm:w-64" + placeholder="Sprint name" + value={newSprint.name} + onChange={(e) => setNewSprint({ ...newSprint, name: e.target.value })} + className="w-full px-3 py-2 bg-slate-800 border border-slate-700 rounded text-sm text-white" /> -
- - -
- -
- - {/* Stats */} -
- - {backlogTasks.length} items in backlog - - - - - {backlogTasks.filter((t) => t.priority === "urgent").length} - {" "} - urgent - - - - - {backlogTasks.filter((t) => t.priority === "high").length} - {" "} - high priority - -
- - {/* Backlog List */} - {sortedTasks.length === 0 ? ( -
- {searchQuery || filterType !== "all" || filterPriority !== "all" ? ( -

No items match your filters

- ) : ( - <> -

Backlog is empty

- - - )} -
- ) : ( -
- t.id)} - strategy={verticalListSortingStrategy} - > - {sortedTasks.map((task) => ( - selectTask(task.id)} - onAssignToSprint={(sprintId) => - handleAssignToSprint(task.id, sprintId) - } +