/ src / components / tasks / task-column.tsx
task-column.tsx
  1  'use client'
  2  
  3  import { useState, useCallback } from 'react'
  4  import { TaskCard } from './task-card'
  5  import { useCreateTaskMutation } from '@/features/tasks/queries'
  6  import type { Agent, BoardTask, BoardTaskStatus, Project } from '@/types'
  7  
  8  const COLUMN_CONFIG: Record<BoardTaskStatus, { label: string; color: string; dot: string }> = {
  9    backlog: { label: 'Backlog', color: 'text-text-3', dot: 'bg-white/20' },
 10    queued: { label: 'Queued', color: 'text-amber-400', dot: 'bg-amber-400' },
 11    running: { label: 'Running', color: 'text-blue-400', dot: 'bg-blue-400' },
 12    completed: { label: 'Completed', color: 'text-emerald-400', dot: 'bg-emerald-400' },
 13    failed: { label: 'Failed', color: 'text-red-400', dot: 'bg-red-400' },
 14    cancelled: { label: 'Cancelled', color: 'text-text-3', dot: 'bg-white/20' },
 15    archived: { label: 'Archived', color: 'text-text-3/50', dot: 'bg-white/10' },
 16    deferred: { label: 'Deferred', color: 'text-orange-400', dot: 'bg-orange-400' },
 17  }
 18  
 19  interface Props {
 20    status: BoardTaskStatus
 21    tasks: BoardTask[]
 22    agents: Record<string, Agent>
 23    projects: Record<string, Project>
 24    tasksById: Record<string, BoardTask>
 25    onDrop: (taskId: string, newStatus: BoardTaskStatus) => void
 26    selectionMode?: boolean
 27    selectedIds?: Set<string>
 28    onToggleSelect?: (id: string) => void
 29    onSelectAll?: () => void
 30  }
 31  
 32  export function TaskColumn({
 33    status,
 34    tasks,
 35    agents,
 36    projects,
 37    tasksById,
 38    onDrop,
 39    selectionMode,
 40    selectedIds,
 41    onToggleSelect,
 42    onSelectAll,
 43  }: Props) {
 44    const config = COLUMN_CONFIG[status]
 45    const [dragOver, setDragOver] = useState(false)
 46    const [quickAddValue, setQuickAddValue] = useState('')
 47    const [adding, setAdding] = useState(false)
 48    const createTaskMutation = useCreateTaskMutation()
 49  
 50    const handleDragOver = useCallback((e: React.DragEvent) => {
 51      e.preventDefault()
 52      e.dataTransfer.dropEffect = 'move'
 53      setDragOver(true)
 54    }, [])
 55  
 56    const handleDragLeave = useCallback(() => {
 57      setDragOver(false)
 58    }, [])
 59  
 60    const handleDrop = useCallback((e: React.DragEvent) => {
 61      e.preventDefault()
 62      setDragOver(false)
 63      const taskId = e.dataTransfer.getData('text/plain')
 64      if (taskId) {
 65        onDrop(taskId, status)
 66      }
 67    }, [onDrop, status])
 68  
 69    const handleQuickAdd = async () => {
 70      const title = quickAddValue.trim()
 71      if (!title || adding) return
 72      setAdding(true)
 73      try {
 74        await createTaskMutation.mutateAsync({ title, description: '', agentId: '', status })
 75        setQuickAddValue('')
 76      } finally {
 77        setAdding(false)
 78      }
 79    }
 80  
 81    const selectedCount = tasks.filter((t) => selectedIds?.has(t.id)).length
 82  
 83    return (
 84      <div
 85        className={`flex-1 min-w-[240px] max-w-[320px] min-h-0 flex flex-col rounded-[16px] transition-colors duration-150 ${
 86          dragOver ? 'bg-accent-bright/[0.04] ring-1 ring-accent-bright/20' : ''
 87        }`}
 88        onDragOver={handleDragOver}
 89        onDragLeave={handleDragLeave}
 90        onDrop={handleDrop}
 91      >
 92        <div className="flex items-center gap-2.5 px-2 mb-3">
 93          <div className={`w-2 h-2 rounded-full ${config.dot}`} />
 94          <span className={`font-display text-[13px] font-600 ${config.color}`}>{config.label}</span>
 95          <span className="text-[12px] text-text-3 ml-auto">{tasks.length}</span>
 96          {selectionMode && tasks.length > 0 && (
 97            <button
 98              onClick={onSelectAll}
 99              className={`text-[10px] font-600 px-1.5 py-0.5 rounded-[5px] cursor-pointer border-none transition-colors
100                ${selectedCount === tasks.length && selectedCount > 0
101                  ? 'bg-accent-bright/20 text-accent-bright'
102                  : 'bg-white/[0.04] text-text-3 hover:bg-white/[0.08]'}`}
103              style={{ fontFamily: 'inherit' }}
104            >
105              {selectedCount === tasks.length && selectedCount > 0 ? 'All' : 'Select all'}
106            </button>
107          )}
108        </div>
109  
110        {/* Quick add input */}
111        {(status === 'backlog' || status === 'queued') && (
112          <div className="px-1 mb-2">
113            <input
114              type="text"
115              value={quickAddValue}
116              onChange={(e) => setQuickAddValue(e.target.value)}
117              onKeyDown={(e) => { if (e.key === 'Enter') handleQuickAdd() }}
118              placeholder={`+ Add to ${config.label.toLowerCase()}...`}
119              className="w-full px-3 py-2 rounded-[10px] bg-white/[0.02] border border-dashed border-white/[0.08] text-[12px] text-text placeholder:text-text-3/30 outline-none focus:border-white/[0.15] focus:bg-white/[0.03] transition-colors"
120              style={{ fontFamily: 'inherit' }}
121              disabled={adding}
122            />
123          </div>
124        )}
125  
126        <div className="flex flex-col gap-3 flex-1 min-h-0 overflow-y-auto overscroll-y-contain touch-pan-y pr-1 px-1 pb-2">
127          {tasks.map((task, idx) => (
128            <TaskCard
129              key={task.id}
130              task={task}
131              agents={agents}
132              projects={projects}
133              tasksById={tasksById}
134              index={idx}
135              selectionMode={selectionMode}
136              selected={selectedIds?.has(task.id)}
137              onToggleSelect={onToggleSelect}
138            />
139          ))}
140          {tasks.length === 0 && (
141            <div className={`text-[12px] text-text-3/50 text-center py-8 rounded-[12px] border border-dashed transition-colors ${
142              dragOver ? 'border-accent-bright/30 text-accent-bright/50' : 'border-transparent'
143            }`}>
144              {dragOver ? 'Drop here' : 'No tasks'}
145            </div>
146          )}
147        </div>
148      </div>
149    )
150  }