Improve backlog drag targets and persistent handle visibility

This commit is contained in:
OpenClaw Bot 2026-02-19 23:03:49 -06:00
parent 957907c87a
commit c5cf7703b8
2 changed files with 45 additions and 21 deletions

View File

@ -33,7 +33,7 @@
"projectId": "2",
"sprintId": "sprint-1",
"createdAt": "2026-02-18T17:01:23.109Z",
"updatedAt": "2026-02-18T17:01:23.109Z",
"updatedAt": "2026-02-20T05:01:04.207Z",
"comments": [
{
"id": "c1",
@ -57,7 +57,6 @@
{
"id": "2",
"title": "MoodWeave App Idea - UPDATED",
"sprintId": "1771551323429",
"projectId": "1",
"status": "backlog",
"priority": "high",
@ -69,7 +68,7 @@
"OpenClaw iOS"
],
"createdAt": "2026-02-18T17:01:23.109Z",
"updatedAt": "2026-02-20T02:28:23.700Z"
"updatedAt": "2026-02-20T05:02:47.264Z"
},
{
"id": "3",
@ -323,7 +322,7 @@
"priority": "low",
"projectId": "3",
"createdAt": "2026-02-18T17:01:23.109Z",
"updatedAt": "2026-02-18T17:01:23.109Z",
"updatedAt": "2026-02-20T05:02:42.002Z",
"comments": [
{
"id": "c52",
@ -472,7 +471,7 @@
]
}
],
"lastUpdated": 1771556223745,
"lastUpdated": 1771563767290,
"sprints": [
{
"name": "Sprint 1",
@ -506,4 +505,4 @@
"createdAt": "2026-02-20T01:37:45.241Z"
}
]
}
}

View File

@ -1,12 +1,13 @@
"use client"
import { useState } from "react"
import { useState, type ReactNode } from "react"
import {
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
PointerSensor,
useDroppable,
useSensor,
useSensors,
closestCorners,
@ -17,8 +18,8 @@ import {
useSortable,
} from "@dnd-kit/sortable"
import { CSS } from "@dnd-kit/utilities"
import { useTaskStore, Task, Sprint } from "@/stores/useTaskStore"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { useTaskStore, Task } from "@/stores/useTaskStore"
import { Card, CardContent } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Plus, GripVertical, ChevronDown, ChevronRight, Calendar } from "lucide-react"
@ -66,13 +67,15 @@ function SortableTaskRow({
<div
ref={setNodeRef}
style={style}
className="flex items-center gap-3 p-3 bg-slate-800/50 border border-slate-700/50 rounded-lg hover:border-slate-600 cursor-pointer group"
className="flex items-center gap-3 p-3 bg-slate-800/50 border border-slate-700/50 rounded-lg hover:border-slate-600 cursor-pointer"
onClick={onClick}
>
<div
{...attributes}
{...listeners}
className="opacity-0 group-hover:opacity-100 transition-opacity cursor-grab active:cursor-grabbing"
className="shrink-0 h-8 w-6 rounded border border-slate-700/70 bg-slate-900/70 flex items-center justify-center cursor-grab active:cursor-grabbing text-slate-400 hover:text-slate-200"
aria-label="Drag task"
title="Drag task"
>
<GripVertical className="w-4 h-4 text-slate-500" />
</div>
@ -106,6 +109,19 @@ function DragOverlayItem({ task }: { task: Task }) {
)
}
function SectionDropZone({ id, children }: { id: string; children: ReactNode }) {
const { isOver, setNodeRef } = useDroppable({ id })
return (
<div
ref={setNodeRef}
className={`rounded-lg transition-colors ${isOver ? "ring-1 ring-blue-500/60 bg-blue-500/5" : ""}`}
>
{children}
</div>
)
}
// Collapsible Section
function TaskSection({
title,
@ -242,14 +258,23 @@ export function BacklogView() {
const taskId = active.id as string
const overId = over.id as string
const overTask = tasks.find((t) => t.id === overId)
const destinationId = overTask
? overTask.sprintId
? currentSprint && overTask.sprintId === currentSprint.id
? "current"
: `sprint-${overTask.sprintId}`
: "backlog"
: overId
// If dropped over a section header, move task to that section's sprint
if (overId === "backlog") {
if (destinationId === "backlog") {
updateTask(taskId, { sprintId: undefined })
} else if (overId === "current" && currentSprint) {
} else if (destinationId === "current" && currentSprint) {
updateTask(taskId, { sprintId: currentSprint.id })
} else if (overId.startsWith("sprint-")) {
const sprintId = overId.replace("sprint-", "")
} else if (destinationId.startsWith("sprint-")) {
const sprintId = destinationId.replace("sprint-", "")
updateTask(taskId, { sprintId })
}
}
@ -283,7 +308,7 @@ export function BacklogView() {
>
<div className="space-y-4">
{/* Current Sprint Section */}
<div id="current">
<SectionDropZone id="current">
<TaskSection
title={currentSprint?.name || "Current Sprint"}
tasks={currentSprintTasks}
@ -305,7 +330,7 @@ export function BacklogView() {
: undefined
}
/>
</div>
</SectionDropZone>
{/* Other Sprints Sections - ordered by start date */}
{otherSprints
@ -314,7 +339,7 @@ export function BacklogView() {
const sprintTasks = tasks.filter((t) => t.sprintId === sprint.id)
console.log(`Sprint ${sprint.name}: ${sprintTasks.length} tasks`, sprintTasks.map(t => t.title))
return (
<div key={sprint.id} id={`sprint-${sprint.id}`}>
<SectionDropZone key={sprint.id} id={`sprint-${sprint.id}`}>
<TaskSection
title={sprint.name}
tasks={sprintTasks}
@ -332,7 +357,7 @@ export function BacklogView() {
status: sprint.status,
}}
/>
</div>
</SectionDropZone>
)
})}
@ -391,7 +416,7 @@ export function BacklogView() {
)}
{/* Backlog Section */}
<div id="backlog">
<SectionDropZone id="backlog">
<TaskSection
title="Backlog"
tasks={backlogTasks}
@ -399,7 +424,7 @@ export function BacklogView() {
onToggle={() => toggleSection("backlog")}
onTaskClick={(task) => selectTask(task.id)}
/>
</div>
</SectionDropZone>
</div>
<DragOverlay>