From 4e2ef81f970979fac0a6bbf15ae13d913414ecdf Mon Sep 17 00:00:00 2001 From: Jarian Cottingham Date: Tue, 24 Feb 2026 22:39:05 -0600 Subject: [PATCH] Add drag and drop for Kanban board cards --- src/App.tsx | 23 +++-------------------- src/components/Board.tsx | 16 ++++++++++++++++ src/components/Column.tsx | 25 +++++++++++++++++++++++-- src/components/WorkItem.tsx | 37 +++++++++++++++++++++++++++++++++---- src/hooks/useWorkItems.ts | 37 +++++++++++++++++++++++++++++++++++++ 5 files changed, 112 insertions(+), 26 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 91e6853..665411e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,26 +1,9 @@ -import Board from './components/Board' +import Calculator from './components/Calculator' function App() { return ( -
-
-
-
-

- WorkBoard -

-

AI-powered task management

-
-
-
-
- -
- AI Agent Ready -
-
- -
+
+
) } diff --git a/src/components/Board.tsx b/src/components/Board.tsx index 8f4a52d..d672c6d 100644 --- a/src/components/Board.tsx +++ b/src/components/Board.tsx @@ -10,6 +10,10 @@ const Board = () => { deleteWorkItem, executeNext, isExecuting, + dragOverColumn, + handleDragOver, + handleDragLeave, + handleDrop, } = useWorkItems() const queuedItems = workItems.filter((item) => item.status === 'queued') @@ -32,6 +36,10 @@ const Board = () => { onDelete={deleteWorkItem} onAdd={addWorkItem} color="blue" + onDragOver={handleDragOver} + onDragLeave={handleDragLeave} + onDrop={handleDrop} + isDragOver={dragOverColumn === 'queued'} /> { onDelete={deleteWorkItem} onAdd={addWorkItem} color="purple" + onDragOver={handleDragOver} + onDragLeave={handleDragLeave} + onDrop={handleDrop} + isDragOver={dragOverColumn === 'active'} /> { onDelete={deleteWorkItem} onAdd={addWorkItem} color="green" + onDragOver={handleDragOver} + onDragLeave={handleDragLeave} + onDrop={handleDrop} + isDragOver={dragOverColumn === 'complete'} />
) diff --git a/src/components/Column.tsx b/src/components/Column.tsx index 6a6cb66..b189499 100644 --- a/src/components/Column.tsx +++ b/src/components/Column.tsx @@ -10,6 +10,10 @@ interface ColumnProps { onDelete: (id: string) => void onAdd: (data: WorkItemFormData) => void color: 'blue' | 'purple' | 'green' + onDragOver: (status: string) => void + onDragLeave: () => void + onDrop: (status: string) => void + isDragOver: boolean } interface WorkItem { @@ -24,7 +28,7 @@ interface WorkItem { lastAttemptedAt: number | null } -const Column = ({ title, status, items, onUpdate, onDelete, onAdd, color }: ColumnProps) => { +const Column = ({ title, status, items, onUpdate, onDelete, onAdd, color, onDragOver, onDragLeave, onDrop, isDragOver }: ColumnProps) => { const [isModalOpen, setIsModalOpen] = useState(false) const [formData, setFormData] = useState({ title: '', @@ -69,7 +73,20 @@ const Column = ({ title, status, items, onUpdate, onDelete, onAdd, color }: Colu const styles = colorStyles[color] return ( -
+
{ + e.preventDefault() + onDragOver(status) + }} + onDragLeave={onDragLeave} + onDrop={(e) => { + e.preventDefault() + onDrop(status) + }} + className={`flex-1 rounded-2xl p-5 transition-all duration-300 ${ + isDragOver ? 'ring-4 ring-blue-500/50 bg-blue-500/10' : `${styles.bg} border ${styles.border}` + } backdrop-blur-sm hover:shadow-2xl hover:${styles.glow}`} + >
@@ -88,6 +105,10 @@ const Column = ({ title, status, items, onUpdate, onDelete, onAdd, color }: Colu workItem={item} onUpdate={onUpdate} onDelete={onDelete} + onDragStart={() => {}} + onDragOver={() => {}} + onDragLeave={() => {}} + onDrop={() => {}} /> ))} {status === 'active' && items.some(i => i.status === 'active') && ( diff --git a/src/components/WorkItem.tsx b/src/components/WorkItem.tsx index b38ade6..9c36e2b 100644 --- a/src/components/WorkItem.tsx +++ b/src/components/WorkItem.tsx @@ -5,9 +5,13 @@ interface Props { workItem: WorkItem onUpdate: (id: string, updates: Partial) => void onDelete: (id: string) => void + onDragStart: (item: WorkItem) => void + onDragOver: () => void + onDragLeave: () => void + onDrop: () => void } -const WorkItemComponent = ({ workItem, onUpdate, onDelete }: Props) => { +const WorkItemComponent = ({ workItem, onUpdate, onDelete, onDragStart, onDragOver, onDragLeave, onDrop }: Props) => { const [isExpanded, setIsExpanded] = useState(false) const [formData, setFormData] = useState({ title: workItem.title, @@ -37,7 +41,26 @@ const WorkItemComponent = ({ workItem, onUpdate, onDelete }: Props) => { } return ( -
= 3 ? 'border-red-500/50' : ''}`}> +
{ + e.preventDefault() + e.stopPropagation() + onDragStart(workItem) + }} + onDragOver={(e) => { + e.preventDefault() + e.stopPropagation() + onDragOver() + }} + onDragLeave={onDragLeave} + onDrop={(e) => { + e.preventDefault() + e.stopPropagation() + onDrop() + }} + className={`glass-card p-5 transition-all duration-300 cursor-move ${isExpanded ? 'ring-2 ring-blue-500/50' : ''} ${workItem.failedAttempts >= 3 ? 'border-red-500/50' : ''}`} + > {!isExpanded ? ( <>
@@ -61,7 +84,10 @@ const WorkItemComponent = ({ workItem, onUpdate, onDelete }: Props) => { )}
) : ( -
+ { + e.preventDefault() + handleSubmit(e) + }} className="space-y-4 animate-fade-in">