Improve backlog drag targets and persistent handle visibility
This commit is contained in:
parent
957907c87a
commit
c5cf7703b8
@ -33,7 +33,7 @@
|
|||||||
"projectId": "2",
|
"projectId": "2",
|
||||||
"sprintId": "sprint-1",
|
"sprintId": "sprint-1",
|
||||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||||
"updatedAt": "2026-02-18T17:01:23.109Z",
|
"updatedAt": "2026-02-20T05:01:04.207Z",
|
||||||
"comments": [
|
"comments": [
|
||||||
{
|
{
|
||||||
"id": "c1",
|
"id": "c1",
|
||||||
@ -57,7 +57,6 @@
|
|||||||
{
|
{
|
||||||
"id": "2",
|
"id": "2",
|
||||||
"title": "MoodWeave App Idea - UPDATED",
|
"title": "MoodWeave App Idea - UPDATED",
|
||||||
"sprintId": "1771551323429",
|
|
||||||
"projectId": "1",
|
"projectId": "1",
|
||||||
"status": "backlog",
|
"status": "backlog",
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
@ -69,7 +68,7 @@
|
|||||||
"OpenClaw iOS"
|
"OpenClaw iOS"
|
||||||
],
|
],
|
||||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||||
"updatedAt": "2026-02-20T02:28:23.700Z"
|
"updatedAt": "2026-02-20T05:02:47.264Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "3",
|
"id": "3",
|
||||||
@ -323,7 +322,7 @@
|
|||||||
"priority": "low",
|
"priority": "low",
|
||||||
"projectId": "3",
|
"projectId": "3",
|
||||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||||
"updatedAt": "2026-02-18T17:01:23.109Z",
|
"updatedAt": "2026-02-20T05:02:42.002Z",
|
||||||
"comments": [
|
"comments": [
|
||||||
{
|
{
|
||||||
"id": "c52",
|
"id": "c52",
|
||||||
@ -472,7 +471,7 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"lastUpdated": 1771556223745,
|
"lastUpdated": 1771563767290,
|
||||||
"sprints": [
|
"sprints": [
|
||||||
{
|
{
|
||||||
"name": "Sprint 1",
|
"name": "Sprint 1",
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState } from "react"
|
import { useState, type ReactNode } from "react"
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
DragEndEvent,
|
DragEndEvent,
|
||||||
DragOverlay,
|
DragOverlay,
|
||||||
DragStartEvent,
|
DragStartEvent,
|
||||||
PointerSensor,
|
PointerSensor,
|
||||||
|
useDroppable,
|
||||||
useSensor,
|
useSensor,
|
||||||
useSensors,
|
useSensors,
|
||||||
closestCorners,
|
closestCorners,
|
||||||
@ -17,8 +18,8 @@ import {
|
|||||||
useSortable,
|
useSortable,
|
||||||
} from "@dnd-kit/sortable"
|
} from "@dnd-kit/sortable"
|
||||||
import { CSS } from "@dnd-kit/utilities"
|
import { CSS } from "@dnd-kit/utilities"
|
||||||
import { useTaskStore, Task, Sprint } from "@/stores/useTaskStore"
|
import { useTaskStore, Task } from "@/stores/useTaskStore"
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Plus, GripVertical, ChevronDown, ChevronRight, Calendar } from "lucide-react"
|
import { Plus, GripVertical, ChevronDown, ChevronRight, Calendar } from "lucide-react"
|
||||||
@ -66,13 +67,15 @@ function SortableTaskRow({
|
|||||||
<div
|
<div
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
style={style}
|
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}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
{...attributes}
|
{...attributes}
|
||||||
{...listeners}
|
{...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" />
|
<GripVertical className="w-4 h-4 text-slate-500" />
|
||||||
</div>
|
</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
|
// Collapsible Section
|
||||||
function TaskSection({
|
function TaskSection({
|
||||||
title,
|
title,
|
||||||
@ -242,14 +258,23 @@ export function BacklogView() {
|
|||||||
|
|
||||||
const taskId = active.id as string
|
const taskId = active.id as string
|
||||||
const overId = over.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 dropped over a section header, move task to that section's sprint
|
||||||
if (overId === "backlog") {
|
if (destinationId === "backlog") {
|
||||||
updateTask(taskId, { sprintId: undefined })
|
updateTask(taskId, { sprintId: undefined })
|
||||||
} else if (overId === "current" && currentSprint) {
|
} else if (destinationId === "current" && currentSprint) {
|
||||||
updateTask(taskId, { sprintId: currentSprint.id })
|
updateTask(taskId, { sprintId: currentSprint.id })
|
||||||
} else if (overId.startsWith("sprint-")) {
|
} else if (destinationId.startsWith("sprint-")) {
|
||||||
const sprintId = overId.replace("sprint-", "")
|
const sprintId = destinationId.replace("sprint-", "")
|
||||||
updateTask(taskId, { sprintId })
|
updateTask(taskId, { sprintId })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -283,7 +308,7 @@ export function BacklogView() {
|
|||||||
>
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Current Sprint Section */}
|
{/* Current Sprint Section */}
|
||||||
<div id="current">
|
<SectionDropZone id="current">
|
||||||
<TaskSection
|
<TaskSection
|
||||||
title={currentSprint?.name || "Current Sprint"}
|
title={currentSprint?.name || "Current Sprint"}
|
||||||
tasks={currentSprintTasks}
|
tasks={currentSprintTasks}
|
||||||
@ -305,7 +330,7 @@ export function BacklogView() {
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</SectionDropZone>
|
||||||
|
|
||||||
{/* Other Sprints Sections - ordered by start date */}
|
{/* Other Sprints Sections - ordered by start date */}
|
||||||
{otherSprints
|
{otherSprints
|
||||||
@ -314,7 +339,7 @@ export function BacklogView() {
|
|||||||
const sprintTasks = tasks.filter((t) => t.sprintId === sprint.id)
|
const sprintTasks = tasks.filter((t) => t.sprintId === sprint.id)
|
||||||
console.log(`Sprint ${sprint.name}: ${sprintTasks.length} tasks`, sprintTasks.map(t => t.title))
|
console.log(`Sprint ${sprint.name}: ${sprintTasks.length} tasks`, sprintTasks.map(t => t.title))
|
||||||
return (
|
return (
|
||||||
<div key={sprint.id} id={`sprint-${sprint.id}`}>
|
<SectionDropZone key={sprint.id} id={`sprint-${sprint.id}`}>
|
||||||
<TaskSection
|
<TaskSection
|
||||||
title={sprint.name}
|
title={sprint.name}
|
||||||
tasks={sprintTasks}
|
tasks={sprintTasks}
|
||||||
@ -332,7 +357,7 @@ export function BacklogView() {
|
|||||||
status: sprint.status,
|
status: sprint.status,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</SectionDropZone>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
||||||
@ -391,7 +416,7 @@ export function BacklogView() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Backlog Section */}
|
{/* Backlog Section */}
|
||||||
<div id="backlog">
|
<SectionDropZone id="backlog">
|
||||||
<TaskSection
|
<TaskSection
|
||||||
title="Backlog"
|
title="Backlog"
|
||||||
tasks={backlogTasks}
|
tasks={backlogTasks}
|
||||||
@ -399,7 +424,7 @@ export function BacklogView() {
|
|||||||
onToggle={() => toggleSection("backlog")}
|
onToggle={() => toggleSection("backlog")}
|
||||||
onTaskClick={(task) => selectTask(task.id)}
|
onTaskClick={(task) => selectTask(task.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</SectionDropZone>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DragOverlay>
|
<DragOverlay>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user