letsbe-hub-dashboard/src/components/tasks/kanban-column.tsx

135 lines
4.0 KiB
TypeScript

'use client'
import { useState } from 'react'
import { cn } from '@/lib/utils'
import { TaskCard } from './task-card'
import { Plus } from 'lucide-react'
import type { VikunjaTask, VikunjaBucket } from '@/lib/vikunja-client'
interface KanbanColumnProps {
bucket: VikunjaBucket
tasks: VikunjaTask[]
onEditTask?: (task: VikunjaTask) => void
onToggleDone?: (task: VikunjaTask) => void
onDragStart?: (e: React.DragEvent, task: VikunjaTask) => void
onDrop?: (bucketId: number) => void
onQuickAdd?: (title: string, bucketId: number) => void
}
export function KanbanColumn({
bucket,
tasks,
onEditTask,
onToggleDone,
onDragStart,
onDrop,
onQuickAdd,
}: KanbanColumnProps) {
const [isDragOver, setIsDragOver] = useState(false)
const [isAdding, setIsAdding] = useState(false)
const [quickTitle, setQuickTitle] = useState('')
function handleDragOver(e: React.DragEvent) {
e.preventDefault()
setIsDragOver(true)
}
function handleDragLeave() {
setIsDragOver(false)
}
function handleDrop(e: React.DragEvent) {
e.preventDefault()
setIsDragOver(false)
onDrop?.(bucket.id)
}
function handleQuickAdd() {
const title = quickTitle.trim()
if (title) {
onQuickAdd?.(title, bucket.id)
setQuickTitle('')
setIsAdding(false)
}
}
return (
<div
className={cn(
'flex w-72 shrink-0 flex-col rounded-lg bg-muted/50',
isDragOver && 'ring-2 ring-primary/50'
)}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<div className="flex items-center justify-between px-3 py-2.5">
<div className="flex items-center gap-2">
<h3 className="text-sm font-semibold">{bucket.title}</h3>
<span className="flex h-5 min-w-[20px] items-center justify-center rounded-full bg-muted px-1.5 text-xs font-medium text-muted-foreground">
{tasks.length}
</span>
</div>
<button
type="button"
onClick={() => setIsAdding(true)}
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
>
<Plus className="h-4 w-4" />
</button>
</div>
<div className="flex-1 space-y-2 overflow-y-auto px-2 pb-2" style={{ maxHeight: 'calc(100vh - 280px)' }}>
{isAdding && (
<div className="rounded-lg border bg-card p-2">
<input
type="text"
value={quickTitle}
onChange={(e) => setQuickTitle(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') handleQuickAdd()
if (e.key === 'Escape') { setIsAdding(false); setQuickTitle('') }
}}
placeholder="Task title..."
className="w-full rounded bg-transparent px-1 py-0.5 text-sm outline-none placeholder:text-muted-foreground"
autoFocus
/>
<div className="mt-1.5 flex justify-end gap-1">
<button
type="button"
onClick={() => { setIsAdding(false); setQuickTitle('') }}
className="rounded px-2 py-1 text-xs text-muted-foreground hover:bg-accent"
>
Cancel
</button>
<button
type="button"
onClick={handleQuickAdd}
className="rounded bg-primary px-2 py-1 text-xs text-primary-foreground hover:bg-primary/90"
>
Add
</button>
</div>
</div>
)}
{tasks.map((task) => (
<TaskCard
key={task.id}
task={task}
onEdit={onEditTask}
onToggleDone={onToggleDone}
onDragStart={onDragStart}
/>
))}
{tasks.length === 0 && !isAdding && (
<div className="py-8 text-center text-xs text-muted-foreground">
Drop tasks here
</div>
)}
</div>
</div>
)
}