1→import { useEffect, useState, useMemo } from 'react'
     2→import { useSearchParams } from 'react-router-dom'
     3→import { Plus, X, GripVertical, Clock, User, RefreshCw, Flag, Pencil, Check, Trash2, Filter, ArrowUpDown, LayoutGrid, List, ChevronLeft, ChevronRight } from 'lucide-react'
     4→import * as Dialog from '@radix-ui/react-dialog'
     5→import * as AlertDialog from '@radix-ui/react-alert-dialog'
     6→import { useProjectStore } from '../stores/projectStore'
     7→import { useTaskStore, Task, TaskCreate, TaskUpdate } from '../stores/taskStore'
     8→import { usePhaseStore } from '../stores/phaseStore'
     9→import { useToastStore } from '../components/Toast'
    10→
    11→const ITEMS_PER_PAGE = 10
    12→
    13→const COLUMNS = [
    14→  { id: 'backlog', title: 'Backlog', color: 'border-slate-500' },
    15→  { id: 'in_progress', title: 'In Progress', color: 'border-accent' },
    16→  { id: 'qa', title: 'QA', color: 'border-primary-400' },
    17→  { id: 'done', title: 'Done', color: 'border-green-400' },
    18→  { id: 'blocked', title: 'Blocked', color: 'border-destructive' },
    19→]
    20→
    21→const PRIORITY_LABELS: Record<number, string> = {
    22→  0: 'Low',
    23→  1: 'Medium',
    24→  2: 'High',
    25→  3: 'Critical',
    26→}
    27→
    28→const STATUS_LABELS: Record<string, string> = {
    29→  backlog: 'Backlog',
    30→  in_progress: 'In Progress',
    31→  qa: 'QA',
    32→  done: 'Done',
    33→  blocked: 'Blocked',
    34→}
    35→
    36→export default function KanbanBoard() {
    37→  const [searchParams, setSearchParams] = useSearchParams()
    38→  const { currentProject } = useProjectStore()
    39→  const { tasks, fetchTasks, createTask, updateTask, updateTaskStatus, deleteTask, isTaskDeleting, isLoading } = useTaskStore()
    40→  const { phases, fetchPhases } = usePhaseStore()
    41→  const { addToast } = useToastStore()
    42→  const [showAddDialog, setShowAddDialog] = useState(false)
    43→  const [newTaskName, setNewTaskName] = useState('')
    44→  const [newTaskDescription, setNewTaskDescription] = useState('')
    45→  const [newTaskPriority, setNewTaskPriority] = useState(0)
    46→  const [newTaskPhaseId, setNewTaskPhaseId] = useState<number | null>(null)
    47→  const [isCreating, setIsCreating] = useState(false)
    48→  const [draggedTask, setDraggedTask] = useState<number | null>(null)
    49→  const [selectedTask, setSelectedTask] = useState<Task | null>(null)
    50→  const [isEditing, setIsEditing] = useState(false)
    51→  const [editName, setEditName] = useState('')
    52→  const [editDescription, setEditDescription] = useState('')
    53→  const [editPriority, setEditPriority] = useState(0)
    54→  const [editPhaseId, setEditPhaseId] = useState<number | null>(null)
    55→  const [isSaving, setIsSaving] = useState(false)
    56→  const [showDeleteDialog, setShowDeleteDialog] = useState(false)
    57→  const [isDeleting, setIsDeleting] = useState(false)
    58→  const [showUnsavedDialog, setShowUnsavedDialog] = useState(false)
    59→  const [pendingCloseAction, setPendingCloseAction] = useState<'close' | 'cancel' | null>(null)
    60→  // Filter and Sort state - initialize from URL params
    61→  const getInitialPriorityFilter = (): number | 'all' => {
    62→    const param = searchParams.get('priority')
    63→    if (param === 'all' || param === null) return 'all'
    64→    const num = parseInt(param, 10)
    65→    return [0, 1, 2, 3].includes(num) ? num : 'all'
    66→  }
    67→  const getInitialSortOrder = (): 'newest' | 'oldest' | 'priority' => {
    68→    const param = searchParams.get('sort')
    69→    if (param === 'newest' || param === 'oldest' || param === 'priority') return param
    70→    return 'newest'
    71→  }
    72→  const getInitialViewMode = (): 'kanban' | 'list' => {
    73→    const param = searchParams.get('view')
    74→    if (param === 'list') return 'list'
    75→    return 'kanban'
    76→  }
    77→
    78→  const [priorityFilter, setPriorityFilter] = useState<number | 'all'>(getInitialPriorityFilter)
    79→  const [sortOrder, setSortOrder] = useState<'newest' | 'oldest' | 'priority'>(getInitialSortOrder)
    80→  // View mode and pagination state
    81→  const [viewMode, setViewMode] = useState<'kanban' | 'list'>(getInitialViewMode)
    82→  const [currentPage, setCurrentPage] = useState(1)
    83→
    84→  useEffect(() => {
    85→    if (currentProject?.id) {
    86→      fetchTasks(currentProject.id)
    87→      fetchPhases(currentProject.id)
    88→    }
    89→  }, [currentProject?.id, fetchTasks, fetchPhases])
    90→
    91→  // Handle deep linking - select task from URL param after tasks are loaded
    92→  useEffect(() => {
    93→    const taskIdParam = searchParams.get('task')
    94→    if (taskIdParam && tasks.length > 0) {
    95→      const taskId = parseInt(taskIdParam, 10)
    96→      const task = tasks.find(t => t.id === taskId)
    97→      if (task) {
    98→        setSelectedTask(task)
    99→      }
   100→    }
   101→  }, [searchParams, tasks])
   102→
   103→  // Update URL when task is selected
   104→  const handleSelectTask = (task: Task) => {
   105→    setSelectedTask(task)
   106→    const newParams = new URLSearchParams(searchParams)
   107→    newParams.set('task', task.id.toString())
   108→    setSearchParams(newParams)
   109→  }
   110→
   111→  // Clear task from URL when panel is closed
   112→  const clearTaskFromUrl = () => {
   113→    const newParams = new URLSearchParams(searchParams)
   114→    newParams.delete('task')
   115→    setSearchParams(newParams)
   116→  }
   117→
   118→  // Update URL when filter changes
   119→  const handlePriorityFilterChange = (value: number | 'all') => {
   120→    setPriorityFilter(value)
   121→    const newParams = new URLSearchParams(searchParams)
   122→    if (value === 'all') {
   123→      newParams.delete('priority')
   124→    } else {
   125→      newParams.set('priority', value.toString())
   126→    }
   127→    setSearchParams(newParams)
   128→  }
   129→
   130→  // Update URL when sort changes
   131→  const handleSortOrderChange = (value: 'newest' | 'oldest' | 'priority') => {
   132→    setSortOrder(value)
   133→    const newParams = new URLSearchParams(searchParams)
   134→    if (value === 'newest') {
   135→      newParams.delete('sort') // default value, no need to store
   136→    } else {
   137→      newParams.set('sort', value)
   138→    }
   139→    setSearchParams(newParams)
   140→  }
   141→
   142→  // Update URL when view mode changes
   143→  const handleViewModeChange = (value: 'kanban' | 'list') => {
   144→    setViewMode(value)
   145→    const newParams = new URLSearchParams(searchParams)
   146→    if (value === 'kanban') {
   147→      newParams.delete('view') // default value, no need to store
   148→    } else {
   149→      newParams.set('view', value)
   150→    }
   151→    setSearchParams(newParams)
   152→  }
   153→
   154→  // Reset all filters to defaults
   155→  const handleResetFilters = () => {
   156→    setPriorityFilter('all')
   157→    setSortOrder('newest')
   158→    setCurrentPage(1)
   159→    const newParams = new URLSearchParams(searchParams)
   160→    newParams.delete('priority')
   161→    newParams.delete('sort')
   162→    setSearchParams(newParams)
   163→  }
   164→
   165→  // Check if filters are active (non-default)
   166→  const hasActiveFilters = priorityFilter !== 'all' || sortOrder !== 'newest'
   167→
   168→  // Filtered and sorted tasks
   169→  const filteredAndSortedTasks = useMemo(() => {
   170→    let result = [...tasks]
   171→
   172→    // Apply priority filter
   173→    if (priorityFilter !== 'all') {
   174→      result = result.filter(task => task.priority === priorityFilter)
   175→    }
   176→
   177→    // Apply sort
   178→    result.sort((a, b) => {
   179→      if (sortOrder === 'newest') {
   180→        return new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
   181→      } else if (sortOrder === 'oldest') {
   182→        return new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
   183→      } else {
   184→        // priority - highest first
   185→        return b.priority - a.priority
   186→      }
   187→    })
   188→
   189→    return result
   190→  }, [tasks, priorityFilter, sortOrder])
   191→
   192→  // Pagination calculations
   193→  const totalPages = Math.ceil(filteredAndSortedTasks.length / ITEMS_PER_PAGE)
   194→  const paginatedTasks = useMemo(() => {
   195→    const startIndex = (currentPage - 1) * ITEMS_PER_PAGE
   196→    return filteredAndSortedTasks.slice(startIndex, startIndex + ITEMS_PER_PAGE)
   197→  }, [filteredAndSortedTasks, currentPage])
   198→
   199→  // Reset page when filter changes
   200→  useEffect(() => {
   201→    setCurrentPage(1)
   202→  }, [priorityFilter, sortOrder])
   203→
   204→  const getTasksByStatus = (status: string) => {
   205→    return filteredAndSortedTasks.filter((task) => task.status === status)
   206→  }
   207→
   208→  const handleCreateTask = async () => {
   209→    if (!currentProject || !newTaskName.trim()) return
   210→    setIsCreating(true)
   211→    try {
   212→      const taskData: TaskCreate = {
   213→        name: newTaskName.trim(),
   214→        description: newTaskDescription.trim() || undefined,
   215→        priority: newTaskPriority,
   216→        phase_id: newTaskPhaseId,
   217→      }
   218→      await createTask(currentProject.id, taskData)
   219→      setNewTaskName('')
   220→      setNewTaskDescription('')
   221→      setNewTaskPriority(0)
   222→      setNewTaskPhaseId(null)
   223→      setShowAddDialog(false)
   224→    } catch (error) {
   225→      console.error('Failed to create task:', error)
   226→      addToast('error', 'Create Failed', 'Failed to create task. Please try again.')
   227→    } finally {
   228→      setIsCreating(false)
   229→    }
   230→  }
   231→
   232→  const handleDragStart = (taskId: number) => {
   233→    setDraggedTask(taskId)
   234→  }
   235→
   236→  const handleDragOver = (e: React.DragEvent) => {
   237→    e.preventDefault()
   238→  }
   239→
   240→  const handleDrop = async (targetStatus: string) => {
   241→    if (draggedTask === null) return
   242→    try {
   243→      await updateTaskStatus(draggedTask, targetStatus)
   244→    } catch (error) {
   245→      console.error('Failed to update task status:', error)
   246→      addToast('error', 'Update Failed', 'Failed to update task status. Please try again.')
   247→    }
   248→    setDraggedTask(null)
   249→  }
   250→
   251→  const handleStartEdit = () => {
   252→    if (!selectedTask) return
   253→    setEditName(selectedTask.name)
   254→    setEditDescription(selectedTask.description || '')
   255→    setEditPriority(selectedTask.priority)
   256→    setEditPhaseId(selectedTask.phase_id)
   257→    setIsEditing(true)
   258→  }
   259→
   260→  // Check if edit form has unsaved changes
   261→  const hasUnsavedChanges = () => {
   262→    if (!selectedTask || !isEditing) return false
   263→    return (
   264→      editName.trim() !== selectedTask.name ||
   265→      (editDescription.trim() || '') !== (selectedTask.description || '') ||
   266→      editPriority !== selectedTask.priority ||
   267→      editPhaseId !== selectedTask.phase_id
   268→    )
   269→  }
   270→
   271→  const handleCancelEdit = () => {
   272→    if (hasUnsavedChanges()) {
   273→      setPendingCloseAction('cancel')
   274→      setShowUnsavedDialog(true)
   275→    } else {
   276→      setIsEditing(false)
   277→    }
   278→  }
   279→
   280→  const handleClosePanel = () => {
   281→    if (hasUnsavedChanges()) {
   282→      setPendingCloseAction('close')
   283→      setShowUnsavedDialog(true)
   284→    } else {
   285→      setSelectedTask(null)
   286→      setIsEditing(false)
   287→      clearTaskFromUrl()
   288→    }
   289→  }
   290→
   291→  const handleDiscardChanges = () => {
   292→    setShowUnsavedDialog(false)
   293→    if (pendingCloseAction === 'close') {
   294→      setSelectedTask(null)
   295→      clearTaskFromUrl()
   296→    }
   297→    setIsEditing(false)
   298→    setPendingCloseAction(null)
   299→  }
   300→
   301→  const handleKeepEditing = () => {
   302→    setShowUnsavedDialog(false)
   303→    setPendingCloseAction(null)
   304→  }
   305→
   306→  const handleSaveEdit = async () => {
   307→    if (!selectedTask) return
   308→    setIsSaving(true)
   309→    try {
   310→      const updates: TaskUpdate = {
   311→        name: editName.trim(),
   312→        description: editDescription.trim() || undefined,
   313→        priority: editPriority,
   314→        phase_id: editPhaseId,
   315→      }
   316→      const updatedTask = await updateTask(selectedTask.id, updates)
   317→      setSelectedTask(updatedTask)
   318→      setIsEditing(false)
   319→    } catch (error) {
   320→      console.error('Failed to update task:', error)
   321→      addToast('error', 'Save Failed', 'Failed to save task changes. Please try again.')
   322→    } finally {
   323→      setIsSaving(false)
   324→    }
   325→  }
   326→
   327→  const handleDeleteTask = async () => {
   328→    if (!selectedTask) return
   329→    // Check if already being deleted (prevents rapid clicks)
   330→    if (isTaskDeleting(selectedTask.id)) return
   331→
   332→    const taskIdToDelete = selectedTask.id
   333→    const taskName = selectedTask.name
   334→    setIsDeleting(true)
   335→    // Close dialog immediately to prevent additional clicks
   336→    setShowDeleteDialog(false)
   337→
   338→    try {
   339→      await deleteTask(taskIdToDelete)
   340→      setSelectedTask(null)
   341→      clearTaskFromUrl()
   342→    } catch (error) {
   343→      console.error('Failed to delete task:', error)
   344→      addToast('error', 'Delete Failed', `Failed to delete task "${taskName}". Please try again.`)
   345→      // Re-open dialog if delete failed so user can retry
   346→      setShowDeleteDialog(true)
   347→    } finally {
   348→      setIsDeleting(false)
   349→    }
   350→  }
   351→
   352→  if (!currentProject) {
   353→    return (
   354→

   355→
Select a project to view its Kanban board

   356→

   357→    )
   358→  }
   359→
   360→  return (
   361→

   362→      {/* Header */}
   363→

   364→
Kanban Board

   365→

   366→          {/* Priority Filter */}
   367→

   368→            <Filter size={16} className="text-muted-foreground" />
   369→            <select
   370→              value={priorityFilter === 'all' ? 'all' : priorityFilter.toString()}
   371→              onChange={(e) => handlePriorityFilterChange(e.target.value === 'all' ? 'all' : Number(e.target.value))}
   372→              className="px-3 py-1.5 bg-card border border-border rounded-lg text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-accent"
   373→            >
   374→              <option value="all">All Priorities</option>
   375→              <option value="3">Critical</option>
   376→              <option value="2">High</option>
   377→              <option value="1">Medium</option>
   378→              <option value="0">Low</option>
   379→            </select>
   380→

   381→
   382→          {/* Sort Order */}
   383→

   384→            <ArrowUpDown size={16} className="text-muted-foreground" />
   385→            <select
   386→              value={sortOrder}
   387→              onChange={(e) => handleSortOrderChange(e.target.value as 'newest' | 'oldest' | 'priority')}
   388→              className="px-3 py-1.5 bg-card border border-border rounded-lg text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-accent"
   389→            >
   390→              <option value="newest">Newest First</option>
   391→              <option value="oldest">Oldest First</option>
   392→              <option value="priority">By Priority</option>
   393→            </select>
   394→

   395→
   396→          {/* Reset Filters Button */}
   397→          {hasActiveFilters && (
   398→            <button
   399→              onClick={handleResetFilters}
   400→              className="flex items-center gap-1.5 px-3 py-1.5 text-sm text-muted-foreground hover:text-foreground hover:bg-secondary rounded-lg transition-colors"
   401→              title="Reset Filters"
   402→            >
   403→              <RefreshCw size={14} />
   404→              Reset
   405→            </button>
   406→          )}
   407→
   408→          {/* View Mode Toggle */}
   409→

   410→            <button
   411→              onClick={() => handleViewModeChange('kanban')}
   412→              className={`p-1.5 rounded ${viewMode === 'kanban' ? 'bg-accent text-white' : 'text-muted-foreground hover:text-foreground'}`}
   413→              title="Kanban View"
   414→            >
   415→              <LayoutGrid size={18} />
   416→            </button>
   417→            <button
   418→              onClick={() => handleViewModeChange('list')}
   419→              className={`p-1.5 rounded ${viewMode === 'list' ? 'bg-accent text-white' : 'text-muted-foreground hover:text-foreground'}`}
   420→              title="List View"
   421→            >
   422→              <List size={18} />
   423→            </button>
   424→

   425→
   426→        <Dialog.Root open={showAddDialog} onOpenChange={setShowAddDialog}>
   427→          <Dialog.Trigger asChild>
   428→            <button className="btn-accent flex items-center gap-2">
   429→              <Plus size={18} />
   430→              Add Task
   431→            </button>
   432→          </Dialog.Trigger>
   433→          <Dialog.Portal>
   434→            <Dialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
   435→            <Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-card border border-border rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">
   436→

   437→                <Dialog.Title className="text-xl font-semibold text-foreground">
   438→                  Add New Task
   439→                </Dialog.Title>
   440→                <Dialog.Close asChild>
   441→                  <button className="text-muted-foreground hover:text-foreground" aria-label="Close dialog">
   442→                    <X size={20} />
   443→                  </button>
   444→                </Dialog.Close>
   445→

   446→              <Dialog.Description className="text-muted-foreground mb-4">
   447→                Create a new task for this project.
   448→              </Dialog.Description>
   449→

   450→

   451→
   452→                    Task Name *
   453→
   454→                  <input
   455→                    type="text"
   456→                    value={newTaskName}
   457→                    onChange={(e) => setNewTaskName(e.target.value)}
   458→                    className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
   459→                    placeholder="Enter task name"
   460→                  />
   461→

   462→

   463→
   464→                    Description
   465→
   466→                  <textarea
   467→                    value={newTaskDescription}
   468→                    onChange={(e) => setNewTaskDescription(e.target.value)}
   469→                    rows={3}
   470→                    className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent resize-none"
   471→                    placeholder="Enter task description"
   472→                  />
   473→

   474→

   475→
   476→                    Priority
   477→
   478→                  <select
   479→                    value={newTaskPriority}
   480→                    onChange={(e) => setNewTaskPriority(Number(e.target.value))}
   481→                    className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
   482→                  >
   483→                    <option value={0}>Low</option>
   484→                    <option value={1}>Medium</option>
   485→                    <option value={2}>High</option>
   486→                    <option value={3}>Critical</option>
   487→                  </select>
   488→

   489→

   490→
   491→                    Phase
   492→
   493→                  <select
   494→                    value={newTaskPhaseId ?? ''}
   495→                    onChange={(e) => setNewTaskPhaseId(e.target.value ? Number(e.target.value) : null)}
   496→                    className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
   497→                  >
   498→                    <option value="">No Phase</option>
   499→                    {phases.map((phase) => (
   500→                      <option key={phase.id} value={phase.id}>{phase.name}</option>
   501→                    ))}
   502→                  </select>
   503→

   504→

   505→

   506→                <Dialog.Close asChild>
   507→                  <button className="px-4 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors">
   508→                    Cancel
   509→                  </button>
   510→                </Dialog.Close>
   511→                <button
   512→                  onClick={handleCreateTask}
   513→                  disabled={isCreating || !newTaskName.trim()}
   514→                  className="px-4 py-2 text-sm bg-accent hover:bg-accent/80 text-white rounded-lg transition-colors disabled:opacity-50"
   515→                >
   516→                  {isCreating ? 'Creating...' : 'Create Task'}
   517→                </button>
   518→

   519→            </Dialog.Content>
   520→          </Dialog.Portal>
   521→        </Dialog.Root>
   522→

   523→

   524→
   525→      {/* Main Content Area */}
   526→

   527→        {viewMode === 'list' ? (
   528→          /* List View with Pagination */
   529→

   530→            {/* Task List Table */}
   531→

   532→

   533→

   534→

   535→                    Task Name
   536→                    Status
   537→                    Priority
   538→                    Created
   539→

   540→

   541→

   542→                  {isLoading && tasks.length === 0 ? (
   543→

   544→
   545→                        Loading...
   546→
   547→

   548→                  ) : paginatedTasks.length === 0 ? (
   549→

   550→
   551→                        No tasks found
   552→
   553→

   554→                  ) : (
   555→                    paginatedTasks.map((task) => (
   556→
 handleSelectTask(task)}
   559→                        className={`border-t border-border hover:bg-secondary/50 cursor-pointer ${
   560→                          selectedTask?.id === task.id ? 'bg-secondary' : ''
   561→                        }`}
   562→                      >
   563→
   564→
{task.name}

   565→                          {task.description && (
   566→

   567→                              {task.description}
   568→

   569→                          )}
   570→
   571→
   572→
   579→                            {STATUS_LABELS[task.status] || task.status}
   580→
   581→
   582→
   583→
   589→                            {PRIORITY_LABELS[task.priority] || 'Unknown'}
   590→
   591→
   592→
   593→                          {new Date(task.created_at).toLocaleDateString()}
   594→
   595→

   596→                    ))
   597→                  )}
   598→

   599→

   600→

   601→
   602→            {/* Pagination Controls */}
   603→            {totalPages > 1 && (
   604→

   605→

   606→                  Showing {((currentPage - 1) * ITEMS_PER_PAGE) + 1} to {Math.min(currentPage * ITEMS_PER_PAGE, filteredAndSortedTasks.length)} of {filteredAndSortedTasks.length} tasks
   607→

   608→

   609→                  <button
   610→                    onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
   611→                    disabled={currentPage === 1}
   612→                    className="p-2 rounded-lg bg-card border border-border text-muted-foreground hover:text-foreground hover:bg-secondary disabled:opacity-50 disabled:cursor-not-allowed"
   613→                  >
   614→                    <ChevronLeft size={18} />
   615→                  </button>
   616→

   617→                    {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
   618→                      <button
   619→                        key={page}
   620→                        onClick={() => setCurrentPage(page)}
   621→                        className={`w-8 h-8 rounded-lg text-sm ${
   622→                          page === currentPage
   623→                            ? 'bg-accent text-white'
   624→                            : 'bg-card border border-border text-muted-foreground hover:text-foreground hover:bg-secondary'
   625→                        }`}
   626→                      >
   627→                        {page}
   628→                      </button>
   629→                    ))}
   630→

   631→                  <button
   632→                    onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
   633→                    disabled={currentPage === totalPages}
   634→                    className="p-2 rounded-lg bg-card border border-border text-muted-foreground hover:text-foreground hover:bg-secondary disabled:opacity-50 disabled:cursor-not-allowed"
   635→                  >
   636→                    <ChevronRight size={18} />
   637→                  </button>
   638→

   639→

   640→            )}
   641→

   642→        ) : (
   643→        /* Kanban Columns */
   644→        <div className={`flex-1 flex gap-4 overflow-x-auto pb-4 ${selectedTask ? 'mr-0' : ''}`}>
   645→          {COLUMNS.map((column) => (
   646→          <div
   647→            key={column.id}
   648→            className={`flex-shrink-0 w-72 bg-card rounded-lg border-t-2 ${column.color}`}
   649→            onDragOver={handleDragOver}
   650→            onDrop={() => handleDrop(column.id)}
   651→          >
   652→            {/* Column Header */}
   653→

   654→

   655→
{column.title}

   656→
   657→                  {getTasksByStatus(column.id).length}
   658→
   659→

   660→

   661→
   662→            {/* Column Content */}
   663→

   664→              {isLoading && tasks.length === 0 ? (
   665→

   666→                  Loading...
   667→

   668→              ) : getTasksByStatus(column.id).length === 0 ? (
   669→

   670→                  No tasks
   671→

   672→              ) : (
   673→                getTasksByStatus(column.id).map((task) => (
   674→
 handleDragStart(task.id)}
   678→                    onClick={() => handleSelectTask(task)}
   679→                    className={`kanban-card bg-background rounded-lg p-4 border border-border cursor-pointer hover:border-muted-foreground ${
   680→                      draggedTask === task.id ? 'opacity-50' : ''
   681→                    } ${selectedTask?.id === task.id ? 'ring-2 ring-accent' : ''}`}
   682→                  >
   683→

   684→                      <GripVertical size={16} className="text-muted-foreground mt-0.5 flex-shrink-0" />
   685→

   686→
{task.name}

   687→                        {task.description && (
   688→

   689→                            {task.description}
   690→

   691→                        )}
   692→

   693→                          <span
   694→                            className={`badge ${
   695→                              column.id === 'backlog'
   696→                                ? 'badge-backlog'
   697→                                : column.id === 'in_progress'
   698→                                ? 'badge-in-progress'
   699→                                : column.id === 'qa'
   700→                                ? 'badge-qa'
   701→                                : column.id === 'done'
   702→                                ? 'badge-done'
   703→                                : 'badge-blocked'
   704→                            }`}
   705→                          >
   706→                            {column.title}
   707→
   708→
   714→                            {task.priority === 3 ? 'Critical' :
   715→                             task.priority === 2 ? 'High' :
   716→                             task.priority === 1 ? 'Medium' : 'Low'}
   717→
   718→

   719→

   720→

   721→

   722→                ))
   723→              )}
   724→

   725→

   726→          ))}
   727→

   728→        )}
   729→
   730→        {/* Task Details Panel */}
   731→        {selectedTask && (
   732→

   733→

   734→
Task Details

   735→

   736→                {!isEditing && (
   737→                  <>
   738→                    <button
   739→                      onClick={handleStartEdit}
   740→                      className="text-muted-foreground hover:text-accent transition-colors"
   741→                      title="Edit Task"
   742→                    >
   743→                      <Pencil size={18} />
   744→                    </button>
   745→                    <AlertDialog.Root open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
   746→                      <AlertDialog.Trigger asChild>
   747→                        <button
   748→                          className="text-muted-foreground hover:text-destructive transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
   749→                          title="Delete Task"
   750→                          disabled={isDeleting || (selectedTask ? isTaskDeleting(selectedTask.id) : false)}
   751→                        >
   752→                          <Trash2 size={18} />
   753→                        </button>
   754→                      </AlertDialog.Trigger>
   755→                      <AlertDialog.Portal>
   756→                        <AlertDialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
   757→                        <AlertDialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-card border border-border rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">
   758→                          <AlertDialog.Title className="text-xl font-semibold text-foreground">
   759→                            Delete Task
   760→                          </AlertDialog.Title>
   761→                          <AlertDialog.Description className="mt-3 text-muted-foreground">
   762→                            Are you sure you want to delete {selectedTask.name}?
   763→                            This action cannot be undone.
   764→                          </AlertDialog.Description>
   765→

   766→                            <AlertDialog.Cancel asChild>
   767→                              <button className="px-4 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors">
   768→                                Cancel
   769→                              </button>
   770→                            </AlertDialog.Cancel>
   771→                            <AlertDialog.Action asChild>
   772→                              <button
   773→                                onClick={handleDeleteTask}
   774→                                disabled={isDeleting || (selectedTask ? isTaskDeleting(selectedTask.id) : false)}
   775→                                className="px-4 py-2 text-sm bg-destructive hover:bg-destructive/80 text-white rounded-lg transition-colors disabled:opacity-50"
   776→                              >
   777→                                {isDeleting || (selectedTask && isTaskDeleting(selectedTask.id)) ? 'Deleting...' : 'Delete Task'}
   778→                              </button>
   779→                            </AlertDialog.Action>
   780→

   781→                        </AlertDialog.Content>
   782→                      </AlertDialog.Portal>
   783→                    </AlertDialog.Root>
   784→                  </>
   785→                )}
   786→                <button
   787→                  onClick={handleClosePanel}
   788→                  className="text-muted-foreground hover:text-foreground transition-colors"
   789→                  title="Close"
   790→                >
   791→                  <X size={20} />
   792→                </button>
   793→

   794→

   795→

   796→              {isEditing ? (
   797→                /* Edit Form */
   798→

   799→

   800→                    Name *
   801→                    <input
   802→                      type="text"
   803→                      value={editName}
   804→                      onChange={(e) => setEditName(e.target.value)}
   805→                      className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
   806→                      placeholder="Task name"
   807→                    />
   808→

   809→

   810→                    Description
   811→                    <textarea
   812→                      value={editDescription}
   813→                      onChange={(e) => setEditDescription(e.target.value)}
   814→                      rows={3}
   815→                      className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent resize-none"
   816→                      placeholder="Task description"
   817→                    />
   818→

   819→

   820→                    Priority
   821→                    <select
   822→                      value={editPriority}
   823→                      onChange={(e) => setEditPriority(Number(e.target.value))}
   824→                      className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
   825→                    >
   826→                      <option value={0}>Low</option>
   827→                      <option value={1}>Medium</option>
   828→                      <option value={2}>High</option>
   829→                      <option value={3}>Critical</option>
   830→                    </select>
   831→

   832→

   833→                    Phase
   834→                    <select
   835→                      value={editPhaseId ?? ''}
   836→                      onChange={(e) => setEditPhaseId(e.target.value ? Number(e.target.value) : null)}
   837→                      className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
   838→                    >
   839→                      <option value="">No Phase</option>
   840→                      {phases.map((phase) => (
   841→                        <option key={phase.id} value={phase.id}>{phase.name}</option>
   842→                      ))}
   843→                    </select>
   844→

   845→

   846→                    <button
   847→                      onClick={handleCancelEdit}
   848→                      className="flex-1 px-3 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors"
   849→                    >
   850→                      Cancel
   851→                    </button>
   852→                    <button
   853→                      onClick={handleSaveEdit}
   854→                      disabled={isSaving || !editName.trim()}
   855→                      className="flex-1 flex items-center justify-center gap-2 px-3 py-2 text-sm bg-accent hover:bg-accent/80 text-white rounded-lg transition-colors disabled:opacity-50"
   856→                    >
   857→                      {isSaving ? 'Saving...' : <><Check size={16} /> Save</>}
   858→                    </button>
   859→

   860→

   861→              ) : (
   862→                /* View Mode */
   863→                <>
   864→                  {/* Task Name */}
   865→

   866→                    Name
   867→
{selectedTask.name}

   868→

   869→
   870→                  {/* Description */}
   871→

   872→                    Description
   873→

   874→                      {selectedTask.description || No description}
   875→

   876→

   877→
   878→                  {/* Status */}
   879→

   880→                    Status
   881→
   888→                      {STATUS_LABELS[selectedTask.status] || selectedTask.status}
   889→
   890→

   891→
   892→                  {/* Priority */}
   893→

   894→                    Priority
   895→
   901→                      {PRIORITY_LABELS[selectedTask.priority] || 'Unknown'}
   902→
   903→

   904→
   905→                  {/* Phase */}
   906→

   907→                    Phase
   908→

   909→                      {selectedTask.phase_id ? (
   910→                        phases.find(p => p.id === selectedTask.phase_id)?.name || 'Unknown Phase'
   911→                      ) : (
   912→                        No phase
   913→                      )}
   914→

   915→

   916→
   917→                  {/* Agent Assignment */}
   918→

   919→
   920→                      <User size={14} />
   921→                      Agent Assignment
   922→
   923→

   924→                      {selectedTask.agent_type ? (
   925→                        {selectedTask.agent_type.replace('_', ' ')}
   926→                      ) : (
   927→                        Not assigned
   928→                      )}
   929→

   930→

   931→
   932→                  {/* Iteration Count */}
   933→

   934→
   935→                      <RefreshCw size={14} />
   936→                      Iteration Count
   937→
   938→
{selectedTask.iteration_count}

   939→

   940→
   941→                  {/* Flagged for Review */}
   942→

   943→
   944→                      <Flag size={14} />
   945→                      Flagged for Review
   946→
   947→

   948→                      {selectedTask.flagged_for_review ? 'Yes' : 'No'}
   949→

   950→

   951→
   952→                  {/* Time Estimates */}
   953→                  {(selectedTask.estimated_time || selectedTask.actual_time) && (
   954→

   955→
   956→                        <Clock size={14} />
   957→                        Time
   958→
   959→

   960→                        {selectedTask.estimated_time &&
Estimated: {selectedTask.estimated_time} min
}
   961→                        {selectedTask.actual_time &&
Actual: {selectedTask.actual_time} min
}
   962→

   963→

   964→                  )}
   965→
   966→                  {/* Created/Updated timestamps */}
   967→

   968→
Created: {new Date(selectedTask.created_at).toLocaleString()}

   969→
Updated: {new Date(selectedTask.updated_at).toLocaleString()}

   970→

   971→                </>
   972→              )}
   973→

   974→

   975→        )}
   976→
   977→        {/* Unsaved Changes Warning Dialog */}
   978→        <AlertDialog.Root open={showUnsavedDialog} onOpenChange={setShowUnsavedDialog}>
   979→          <AlertDialog.Portal>
   980→            <AlertDialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
   981→            <AlertDialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-card border border-border rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">
   982→              <AlertDialog.Title className="text-xl font-semibold text-foreground">
   983→                Unsaved Changes
   984→              </AlertDialog.Title>
   985→              <AlertDialog.Description className="mt-3 text-muted-foreground">
   986→                You have unsaved changes. Are you sure you want to discard them?
   987→              </AlertDialog.Description>
   988→

   989→                <AlertDialog.Cancel asChild>
   990→                  <button
   991→                    onClick={handleKeepEditing}
   992→                    className="px-4 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors"
   993→                  >
   994→                    Keep Editing
   995→                  </button>
   996→                </AlertDialog.Cancel>
   997→                <AlertDialog.Action asChild>
   998→                  <button
   999→                    onClick={handleDiscardChanges}
  1000→                    className="px-4 py-2 text-sm bg-destructive hover:bg-destructive/80 text-white rounded-lg transition-colors"
  1001→                  >
  1002→                    Discard Changes
  1003→                  </button>
  1004→                </AlertDialog.Action>
  1005→

  1006→            </AlertDialog.Content>
  1007→          </AlertDialog.Portal>
  1008→        </AlertDialog.Root>
  1009→

  1010→

  1011→  )
  1012→}
  1013→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import { useEffect, useState, useMemo } from 'react'
import { useSearchParams } from 'react-router-dom'
import { Plus, X, GripVertical, Clock, User, RefreshCw, Flag, Pencil, Check, Trash2, Filter, ArrowUpDown, LayoutGrid, List, ChevronLeft, ChevronRight } from 'lucide-react'
import * as Dialog from '@radix-ui/react-dialog'
import * as AlertDialog from '@radix-ui/react-alert-dialog'
import { useProjectStore } from '../stores/projectStore'
import { useTaskStore, Task, TaskCreate, TaskUpdate } from '../stores/taskStore'
import { usePhaseStore } from '../stores/phaseStore'
import { useToastStore } from '../components/Toast'

const ITEMS_PER_PAGE = 10

const COLUMNS = [
  { id: 'backlog', title: 'Backlog', color: 'border-slate-500' },
  { id: 'in_progress', title: 'In Progress', color: 'border-accent' },
  { id: 'qa', title: 'QA', color: 'border-primary-400' },
  { id: 'done', title: 'Done', color: 'border-green-400' },
  { id: 'blocked', title: 'Blocked', color: 'border-destructive' },
]

const PRIORITY_LABELS: Record<number, string> = {
  0: 'Low',
  1: 'Medium',
  2: 'High',
  3: 'Critical',
}

const STATUS_LABELS: Record<string, string> = {
  backlog: 'Backlog',
  in_progress: 'In Progress',
  qa: 'QA',
  done: 'Done',
  blocked: 'Blocked',
}

export default function KanbanBoard() {
  const [searchParams, setSearchParams] = useSearchParams()
  const { currentProject } = useProjectStore()
  const { tasks, fetchTasks, createTask, updateTask, updateTaskStatus, deleteTask, isTaskDeleting, isLoading } = useTaskStore()
  const { phases, fetchPhases } = usePhaseStore()
  const { addToast } = useToastStore()
  const [showAddDialog, setShowAddDialog] = useState(false)
  const [newTaskName, setNewTaskName] = useState('')
  const [newTaskDescription, setNewTaskDescription] = useState('')
  const [newTaskPriority, setNewTaskPriority] = useState(0)
  const [newTaskPhaseId, setNewTaskPhaseId] = useState<number | null>(null)
  const [isCreating, setIsCreating] = useState(false)
  const [draggedTask, setDraggedTask] = useState<number | null>(null)
  const [selectedTask, setSelectedTask] = useState<Task | null>(null)
  const [isEditing, setIsEditing] = useState(false)
  const [editName, setEditName] = useState('')
  const [editDescription, setEditDescription] = useState('')
  const [editPriority, setEditPriority] = useState(0)
  const [editPhaseId, setEditPhaseId] = useState<number | null>(null)
  const [isSaving, setIsSaving] = useState(false)
  const [showDeleteDialog, setShowDeleteDialog] = useState(false)
  const [isDeleting, setIsDeleting] = useState(false)
  const [showUnsavedDialog, setShowUnsavedDialog] = useState(false)
  const [pendingCloseAction, setPendingCloseAction] = useState<'close' | 'cancel' | null>(null)
  // Filter and Sort state - initialize from URL params
  const getInitialPriorityFilter = (): number | 'all' => {
    const param = searchParams.get('priority')
    if (param === 'all' || param === null) return 'all'
    const num = parseInt(param, 10)
    return [0, 1, 2, 3].includes(num) ? num : 'all'
  }
  const getInitialSortOrder = (): 'newest' | 'oldest' | 'priority' => {
    const param = searchParams.get('sort')
    if (param === 'newest' || param === 'oldest' || param === 'priority') return param
    return 'newest'
  }
  const getInitialViewMode = (): 'kanban' | 'list' => {
    const param = searchParams.get('view')
    if (param === 'list') return 'list'
    return 'kanban'
  }

  const [priorityFilter, setPriorityFilter] = useState<number | 'all'>(getInitialPriorityFilter)
  const [sortOrder, setSortOrder] = useState<'newest' | 'oldest' | 'priority'>(getInitialSortOrder)
  // View mode and pagination state
  const [viewMode, setViewMode] = useState<'kanban' | 'list'>(getInitialViewMode)
  const [currentPage, setCurrentPage] = useState(1)

  useEffect(() => {
    if (currentProject?.id) {
      fetchTasks(currentProject.id)
      fetchPhases(currentProject.id)
    }
  }, [currentProject?.id, fetchTasks, fetchPhases])

  // Handle deep linking - select task from URL param after tasks are loaded
  useEffect(() => {
    const taskIdParam = searchParams.get('task')
    if (taskIdParam && tasks.length > 0) {
      const taskId = parseInt(taskIdParam, 10)
      const task = tasks.find(t => t.id === taskId)
      if (task) {
        setSelectedTask(task)
      }
    }
  }, [searchParams, tasks])

  // Update URL when task is selected
  const handleSelectTask = (task: Task) => {
    setSelectedTask(task)
    const newParams = new URLSearchParams(searchParams)
    newParams.set('task', task.id.toString())
    setSearchParams(newParams)
  }

  // Clear task from URL when panel is closed
  const clearTaskFromUrl = () => {
    const newParams = new URLSearchParams(searchParams)
    newParams.delete('task')
    setSearchParams(newParams)
  }

  // Update URL when filter changes
  const handlePriorityFilterChange = (value: number | 'all') => {
    setPriorityFilter(value)
    const newParams = new URLSearchParams(searchParams)
    if (value === 'all') {
      newParams.delete('priority')
    } else {
      newParams.set('priority', value.toString())
    }
    setSearchParams(newParams)
  }

  // Update URL when sort changes
  const handleSortOrderChange = (value: 'newest' | 'oldest' | 'priority') => {
    setSortOrder(value)
    const newParams = new URLSearchParams(searchParams)
    if (value === 'newest') {
      newParams.delete('sort') // default value, no need to store
    } else {
      newParams.set('sort', value)
    }
    setSearchParams(newParams)
  }

  // Update URL when view mode changes
  const handleViewModeChange = (value: 'kanban' | 'list') => {
    setViewMode(value)
    const newParams = new URLSearchParams(searchParams)
    if (value === 'kanban') {
      newParams.delete('view') // default value, no need to store
    } else {
      newParams.set('view', value)
    }
    setSearchParams(newParams)
  }

  // Reset all filters to defaults
  const handleResetFilters = () => {
    setPriorityFilter('all')
    setSortOrder('newest')
    setCurrentPage(1)
    const newParams = new URLSearchParams(searchParams)
    newParams.delete('priority')
    newParams.delete('sort')
    setSearchParams(newParams)
  }

  // Check if filters are active (non-default)
  const hasActiveFilters = priorityFilter !== 'all' || sortOrder !== 'newest'

  // Filtered and sorted tasks
  const filteredAndSortedTasks = useMemo(() => {
    let result = [...tasks]

    // Apply priority filter
    if (priorityFilter !== 'all') {
      result = result.filter(task => task.priority === priorityFilter)
    }

    // Apply sort
    result.sort((a, b) => {
      if (sortOrder === 'newest') {
        return new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
      } else if (sortOrder === 'oldest') {
        return new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
      } else {
        // priority - highest first
        return b.priority - a.priority
      }
    })

    return result
  }, [tasks, priorityFilter, sortOrder])

  // Pagination calculations
  const totalPages = Math.ceil(filteredAndSortedTasks.length / ITEMS_PER_PAGE)
  const paginatedTasks = useMemo(() => {
    const startIndex = (currentPage - 1) * ITEMS_PER_PAGE
    return filteredAndSortedTasks.slice(startIndex, startIndex + ITEMS_PER_PAGE)
  }, [filteredAndSortedTasks, currentPage])

  // Reset page when filter changes
  useEffect(() => {
    setCurrentPage(1)
  }, [priorityFilter, sortOrder])

  const getTasksByStatus = (status: string) => {
    return filteredAndSortedTasks.filter((task) => task.status === status)
  }

  const handleCreateTask = async () => {
    if (!currentProject || !newTaskName.trim()) return
    setIsCreating(true)
    try {
      const taskData: TaskCreate = {
        name: newTaskName.trim(),
        description: newTaskDescription.trim() || undefined,
        priority: newTaskPriority,
        phase_id: newTaskPhaseId,
      }
      await createTask(currentProject.id, taskData)
      setNewTaskName('')
      setNewTaskDescription('')
      setNewTaskPriority(0)
      setNewTaskPhaseId(null)
      setShowAddDialog(false)
    } catch (error) {
      console.error('Failed to create task:', error)
      addToast('error', 'Create Failed', 'Failed to create task. Please try again.')
    } finally {
      setIsCreating(false)
    }
  }

  const handleDragStart = (taskId: number) => {
    setDraggedTask(taskId)
  }

  const handleDragOver = (e: React.DragEvent) => {
    e.preventDefault()
  }

  const handleDrop = async (targetStatus: string) => {
    if (draggedTask === null) return
    try {
      await updateTaskStatus(draggedTask, targetStatus)
    } catch (error) {
      console.error('Failed to update task status:', error)
      addToast('error', 'Update Failed', 'Failed to update task status. Please try again.')
    }
    setDraggedTask(null)
  }

  const handleStartEdit = () => {
    if (!selectedTask) return
    setEditName(selectedTask.name)
    setEditDescription(selectedTask.description || '')
    setEditPriority(selectedTask.priority)
    setEditPhaseId(selectedTask.phase_id)
    setIsEditing(true)
  }

  // Check if edit form has unsaved changes
  const hasUnsavedChanges = () => {
    if (!selectedTask || !isEditing) return false
    return (
      editName.trim() !== selectedTask.name ||
      (editDescription.trim() || '') !== (selectedTask.description || '') ||
      editPriority !== selectedTask.priority ||
      editPhaseId !== selectedTask.phase_id
    )
  }

  const handleCancelEdit = () => {
    if (hasUnsavedChanges()) {
      setPendingCloseAction('cancel')
      setShowUnsavedDialog(true)
    } else {
      setIsEditing(false)
    }
  }

  const handleClosePanel = () => {
    if (hasUnsavedChanges()) {
      setPendingCloseAction('close')
      setShowUnsavedDialog(true)
    } else {
      setSelectedTask(null)
      setIsEditing(false)
      clearTaskFromUrl()
    }
  }

  const handleDiscardChanges = () => {
    setShowUnsavedDialog(false)
    if (pendingCloseAction === 'close') {
      setSelectedTask(null)
      clearTaskFromUrl()
    }
    setIsEditing(false)
    setPendingCloseAction(null)
  }

  const handleKeepEditing = () => {
    setShowUnsavedDialog(false)
    setPendingCloseAction(null)
  }

  const handleSaveEdit = async () => {
    if (!selectedTask) return
    setIsSaving(true)
    try {
      const updates: TaskUpdate = {
        name: editName.trim(),
        description: editDescription.trim() || undefined,
        priority: editPriority,
        phase_id: editPhaseId,
      }
      const updatedTask = await updateTask(selectedTask.id, updates)
      setSelectedTask(updatedTask)
      setIsEditing(false)
    } catch (error) {
      console.error('Failed to update task:', error)
      addToast('error', 'Save Failed', 'Failed to save task changes. Please try again.')
    } finally {
      setIsSaving(false)
    }
  }

  const handleDeleteTask = async () => {
    if (!selectedTask) return
    // Check if already being deleted (prevents rapid clicks)
    if (isTaskDeleting(selectedTask.id)) return

    const taskIdToDelete = selectedTask.id
    const taskName = selectedTask.name
    setIsDeleting(true)
    // Close dialog immediately to prevent additional clicks
    setShowDeleteDialog(false)

    try {
      await deleteTask(taskIdToDelete)
      setSelectedTask(null)
      clearTaskFromUrl()
    } catch (error) {
      console.error('Failed to delete task:', error)
      addToast('error', 'Delete Failed', `Failed to delete task "${taskName}". Please try again.`)
      // Re-open dialog if delete failed so user can retry
      setShowDeleteDialog(true)
    } finally {
      setIsDeleting(false)
    }
  }

  if (!currentProject) {
    return (

Select a project to view its Kanban board

    )
  }

  return (

      {/* Header */}

Kanban Board

          {/* Priority Filter */}

            <Filter size={16} className="text-muted-foreground" />
            <select
              value={priorityFilter === 'all' ? 'all' : priorityFilter.toString()}
              onChange={(e) => handlePriorityFilterChange(e.target.value === 'all' ? 'all' : Number(e.target.value))}
              className="px-3 py-1.5 bg-card border border-border rounded-lg text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-accent"
            >
              <option value="all">All Priorities</option>
              <option value="3">Critical</option>
              <option value="2">High</option>
              <option value="1">Medium</option>
              <option value="0">Low</option>
            </select>

          {/* Sort Order */}

            <ArrowUpDown size={16} className="text-muted-foreground" />
            <select
              value={sortOrder}
              onChange={(e) => handleSortOrderChange(e.target.value as 'newest' | 'oldest' | 'priority')}
              className="px-3 py-1.5 bg-card border border-border rounded-lg text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-accent"
            >
              <option value="newest">Newest First</option>
              <option value="oldest">Oldest First</option>
              <option value="priority">By Priority</option>
            </select>

          {/* Reset Filters Button */}
          {hasActiveFilters && (
            <button
              onClick={handleResetFilters}
              className="flex items-center gap-1.5 px-3 py-1.5 text-sm text-muted-foreground hover:text-foreground hover:bg-secondary rounded-lg transition-colors"
              title="Reset Filters"
            >
              <RefreshCw size={14} />
              Reset
            </button>
          )}

          {/* View Mode Toggle */}

            <button
              onClick={() => handleViewModeChange('kanban')}
              className={`p-1.5 rounded ${viewMode === 'kanban' ? 'bg-accent text-white' : 'text-muted-foreground hover:text-foreground'}`}
              title="Kanban View"
            >
              <LayoutGrid size={18} />
            </button>
            <button
              onClick={() => handleViewModeChange('list')}
              className={`p-1.5 rounded ${viewMode === 'list' ? 'bg-accent text-white' : 'text-muted-foreground hover:text-foreground'}`}
              title="List View"
            >
              <List size={18} />
            </button>

        <Dialog.Root open={showAddDialog} onOpenChange={setShowAddDialog}>
          <Dialog.Trigger asChild>
            <button className="btn-accent flex items-center gap-2">
              <Plus size={18} />
              Add Task
            </button>
          </Dialog.Trigger>
          <Dialog.Portal>
            <Dialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
            <Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-card border border-border rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">

                <Dialog.Title className="text-xl font-semibold text-foreground">
                  Add New Task
                </Dialog.Title>
                <Dialog.Close asChild>
                  <button className="text-muted-foreground hover:text-foreground" aria-label="Close dialog">
                    <X size={20} />
                  </button>
                </Dialog.Close>

              <Dialog.Description className="text-muted-foreground mb-4">
                Create a new task for this project.
              </Dialog.Description>

                    Task Name *

                  <input
                    type="text"
                    value={newTaskName}
                    onChange={(e) => setNewTaskName(e.target.value)}
                    className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
                    placeholder="Enter task name"
                  />

                    Description

                  <textarea
                    value={newTaskDescription}
                    onChange={(e) => setNewTaskDescription(e.target.value)}
                    rows={3}
                    className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent resize-none"
                    placeholder="Enter task description"
                  />

                    Priority

                  <select
                    value={newTaskPriority}
                    onChange={(e) => setNewTaskPriority(Number(e.target.value))}
                    className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
                  >
                    <option value={0}>Low</option>
                    <option value={1}>Medium</option>
                    <option value={2}>High</option>
                    <option value={3}>Critical</option>
                  </select>

                    Phase

                  <select
                    value={newTaskPhaseId ?? ''}
                    onChange={(e) => setNewTaskPhaseId(e.target.value ? Number(e.target.value) : null)}
                    className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
                  >
                    <option value="">No Phase</option>
                    {phases.map((phase) => (
                      <option key={phase.id} value={phase.id}>{phase.name}</option>
                    ))}
                  </select>

                <Dialog.Close asChild>
                  <button className="px-4 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors">
                    Cancel
                  </button>
                </Dialog.Close>
                <button
                  onClick={handleCreateTask}
                  disabled={isCreating || !newTaskName.trim()}
                  className="px-4 py-2 text-sm bg-accent hover:bg-accent/80 text-white rounded-lg transition-colors disabled:opacity-50"
                >
                  {isCreating ? 'Creating...' : 'Create Task'}
                </button>

            </Dialog.Content>
          </Dialog.Portal>
        </Dialog.Root>

      {/* Main Content Area */}

        {viewMode === 'list' ? (
          /* List View with Pagination */

            {/* Task List Table */}

                    Task Name
                    Status
                    Priority
                    Created

                  {isLoading && tasks.length === 0 ? (

                        Loading...

                  ) : paginatedTasks.length === 0 ? (

                        No tasks found

                  ) : (
                    paginatedTasks.map((task) => (

 handleSelectTask(task)}
                        className={`border-t border-border hover:bg-secondary/50 cursor-pointer ${
                          selectedTask?.id === task.id ? 'bg-secondary' : ''
                        }`}
                      >

{task.name}

                          {task.description && (

                              {task.description}

                          )}

                            {STATUS_LABELS[task.status] || task.status}

                            {PRIORITY_LABELS[task.priority] || 'Unknown'}

                          {new Date(task.created_at).toLocaleDateString()}

                    ))
                  )}

            {/* Pagination Controls */}
            {totalPages > 1 && (

                  Showing {((currentPage - 1) * ITEMS_PER_PAGE) + 1} to {Math.min(currentPage * ITEMS_PER_PAGE, filteredAndSortedTasks.length)} of {filteredAndSortedTasks.length} tasks

                  <button
                    onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
                    disabled={currentPage === 1}
                    className="p-2 rounded-lg bg-card border border-border text-muted-foreground hover:text-foreground hover:bg-secondary disabled:opacity-50 disabled:cursor-not-allowed"
                  >
                    <ChevronLeft size={18} />
                  </button>

                    {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
                      <button
                        key={page}
                        onClick={() => setCurrentPage(page)}
                        className={`w-8 h-8 rounded-lg text-sm ${
                          page === currentPage
                            ? 'bg-accent text-white'
                            : 'bg-card border border-border text-muted-foreground hover:text-foreground hover:bg-secondary'
                        }`}
                      >
                        {page}
                      </button>
                    ))}

                  <button
                    onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
                    disabled={currentPage === totalPages}
                    className="p-2 rounded-lg bg-card border border-border text-muted-foreground hover:text-foreground hover:bg-secondary disabled:opacity-50 disabled:cursor-not-allowed"
                  >
                    <ChevronRight size={18} />
                  </button>

            )}

        ) : (
        /* Kanban Columns */
        <div className={`flex-1 flex gap-4 overflow-x-auto pb-4 ${selectedTask ? 'mr-0' : ''}`}>
          {COLUMNS.map((column) => (
          <div
            key={column.id}
            className={`flex-shrink-0 w-72 bg-card rounded-lg border-t-2 ${column.color}`}
            onDragOver={handleDragOver}
            onDrop={() => handleDrop(column.id)}
          >
            {/* Column Header */}

{column.title}

                  {getTasksByStatus(column.id).length}

            {/* Column Content */}

              {isLoading && tasks.length === 0 ? (

                  Loading...

              ) : getTasksByStatus(column.id).length === 0 ? (

                  No tasks

              ) : (
                getTasksByStatus(column.id).map((task) => (

 handleDragStart(task.id)}
                    onClick={() => handleSelectTask(task)}
                    className={`kanban-card bg-background rounded-lg p-4 border border-border cursor-pointer hover:border-muted-foreground ${
                      draggedTask === task.id ? 'opacity-50' : ''
                    } ${selectedTask?.id === task.id ? 'ring-2 ring-accent' : ''}`}
                  >

                      <GripVertical size={16} className="text-muted-foreground mt-0.5 flex-shrink-0" />

{task.name}

                        {task.description && (

                            {task.description}

                        )}

                          <span
                            className={`badge ${
                              column.id === 'backlog'
                                ? 'badge-backlog'
                                : column.id === 'in_progress'
                                ? 'badge-in-progress'
                                : column.id === 'qa'
                                ? 'badge-qa'
                                : column.id === 'done'
                                ? 'badge-done'
                                : 'badge-blocked'
                            }`}
                          >
                            {column.title}

                            {task.priority === 3 ? 'Critical' :
                             task.priority === 2 ? 'High' :
                             task.priority === 1 ? 'Medium' : 'Low'}

                ))
              )}

          ))}

        )}

        {/* Task Details Panel */}
        {selectedTask && (

Task Details

                {!isEditing && (
                  <>
                    <button
                      onClick={handleStartEdit}
                      className="text-muted-foreground hover:text-accent transition-colors"
                      title="Edit Task"
                    >
                      <Pencil size={18} />
                    </button>
                    <AlertDialog.Root open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
                      <AlertDialog.Trigger asChild>
                        <button
                          className="text-muted-foreground hover:text-destructive transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
                          title="Delete Task"
                          disabled={isDeleting || (selectedTask ? isTaskDeleting(selectedTask.id) : false)}
                        >
                          <Trash2 size={18} />
                        </button>
                      </AlertDialog.Trigger>
                      <AlertDialog.Portal>
                        <AlertDialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
                        <AlertDialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-card border border-border rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">
                          <AlertDialog.Title className="text-xl font-semibold text-foreground">
                            Delete Task
                          </AlertDialog.Title>
                          <AlertDialog.Description className="mt-3 text-muted-foreground">
                            Are you sure you want to delete {selectedTask.name}?
                            This action cannot be undone.
                          </AlertDialog.Description>

                            <AlertDialog.Cancel asChild>
                              <button className="px-4 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors">
                                Cancel
                              </button>
                            </AlertDialog.Cancel>
                            <AlertDialog.Action asChild>
                              <button
                                onClick={handleDeleteTask}
                                disabled={isDeleting || (selectedTask ? isTaskDeleting(selectedTask.id) : false)}
                                className="px-4 py-2 text-sm bg-destructive hover:bg-destructive/80 text-white rounded-lg transition-colors disabled:opacity-50"
                              >
                                {isDeleting || (selectedTask && isTaskDeleting(selectedTask.id)) ? 'Deleting...' : 'Delete Task'}
                              </button>
                            </AlertDialog.Action>

                        </AlertDialog.Content>
                      </AlertDialog.Portal>
                    </AlertDialog.Root>
                  </>
                )}
                <button
                  onClick={handleClosePanel}
                  className="text-muted-foreground hover:text-foreground transition-colors"
                  title="Close"
                >
                  <X size={20} />
                </button>

              {isEditing ? (
                /* Edit Form */

                    Name *
                    <input
                      type="text"
                      value={editName}
                      onChange={(e) => setEditName(e.target.value)}
                      className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
                      placeholder="Task name"
                    />

                    Description
                    <textarea
                      value={editDescription}
                      onChange={(e) => setEditDescription(e.target.value)}
                      rows={3}
                      className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent resize-none"
                      placeholder="Task description"
                    />

                    Priority
                    <select
                      value={editPriority}
                      onChange={(e) => setEditPriority(Number(e.target.value))}
                      className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
                    >
                      <option value={0}>Low</option>
                      <option value={1}>Medium</option>
                      <option value={2}>High</option>
                      <option value={3}>Critical</option>
                    </select>

                    Phase
                    <select
                      value={editPhaseId ?? ''}
                      onChange={(e) => setEditPhaseId(e.target.value ? Number(e.target.value) : null)}
                      className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
                    >
                      <option value="">No Phase</option>
                      {phases.map((phase) => (
                        <option key={phase.id} value={phase.id}>{phase.name}</option>
                      ))}
                    </select>

                    <button
                      onClick={handleCancelEdit}
                      className="flex-1 px-3 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors"
                    >
                      Cancel
                    </button>
                    <button
                      onClick={handleSaveEdit}
                      disabled={isSaving || !editName.trim()}
                      className="flex-1 flex items-center justify-center gap-2 px-3 py-2 text-sm bg-accent hover:bg-accent/80 text-white rounded-lg transition-colors disabled:opacity-50"
                    >
                      {isSaving ? 'Saving...' : <><Check size={16} /> Save</>}
                    </button>

              ) : (
                /* View Mode */
                <>
                  {/* Task Name */}

                    Name

{selectedTask.name}

                  {/* Description */}

                    Description

                      {selectedTask.description || No description}

                  {/* Status */}

                    Status

                      {STATUS_LABELS[selectedTask.status] || selectedTask.status}

                  {/* Priority */}

                    Priority

                      {PRIORITY_LABELS[selectedTask.priority] || 'Unknown'}

                  {/* Phase */}

                    Phase

                      {selectedTask.phase_id ? (
                        phases.find(p => p.id === selectedTask.phase_id)?.name || 'Unknown Phase'
                      ) : (
                        No phase
                      )}

                  {/* Agent Assignment */}

                      <User size={14} />
                      Agent Assignment

                      {selectedTask.agent_type ? (
                        {selectedTask.agent_type.replace('_', ' ')}
                      ) : (
                        Not assigned
                      )}

                  {/* Iteration Count */}

                      <RefreshCw size={14} />
                      Iteration Count

{selectedTask.iteration_count}

                  {/* Flagged for Review */}

                      <Flag size={14} />
                      Flagged for Review

                      {selectedTask.flagged_for_review ? 'Yes' : 'No'}

                  {/* Time Estimates */}
                  {(selectedTask.estimated_time || selectedTask.actual_time) && (

                        <Clock size={14} />
                        Time

                        {selectedTask.estimated_time &&
Estimated: {selectedTask.estimated_time} min
}
                        {selectedTask.actual_time &&
Actual: {selectedTask.actual_time} min
}

                  )}

                  {/* Created/Updated timestamps */}

Created: {new Date(selectedTask.created_at).toLocaleString()}

Updated: {new Date(selectedTask.updated_at).toLocaleString()}

                </>
              )}

        )}

        {/* Unsaved Changes Warning Dialog */}
        <AlertDialog.Root open={showUnsavedDialog} onOpenChange={setShowUnsavedDialog}>
          <AlertDialog.Portal>
            <AlertDialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
            <AlertDialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-card border border-border rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">
              <AlertDialog.Title className="text-xl font-semibold text-foreground">
                Unsaved Changes
              </AlertDialog.Title>
              <AlertDialog.Description className="mt-3 text-muted-foreground">
                You have unsaved changes. Are you sure you want to discard them?
              </AlertDialog.Description>

                <AlertDialog.Cancel asChild>
                  <button
                    onClick={handleKeepEditing}
                    className="px-4 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors"
                  >
                    Keep Editing
                  </button>
                </AlertDialog.Cancel>
                <AlertDialog.Action asChild>
                  <button
                    onClick={handleDiscardChanges}
                    className="px-4 py-2 text-sm bg-destructive hover:bg-destructive/80 text-white rounded-lg transition-colors"
                  >
                    Discard Changes
                  </button>
                </AlertDialog.Action>

            </AlertDialog.Content>
          </AlertDialog.Portal>
        </AlertDialog.Root>

  )
}


Now I understand the current structure. I can see there's already a priority filter, but no status filter. I need to add a status filter dropdown. Let me add it:


Edit

  const getInitialPriorityFilter = (): number | 'all' => {
    const param = searchParams.get('priority')
    if (param === 'all' || param === null) return 'all'
    const num = parseInt(param, 10)
    return [0, 1, 2, 3].includes(num) ? num : 'all'
  }
  const getInitialSortOrder = (): 'newest' | 'oldest' | 'priority' => {
    const param = searchParams.get('sort')
    if (param === 'newest' || param === 'oldest' || param === 'priority') return param
    return 'newest'
  }
  const getInitialViewMode = (): 'kanban' | 'list' => {
    const param = searchParams.get('view')
    if (param === 'list') return 'list'
    return 'kanban'
  }

  const [priorityFilter, setPriorityFilter] = useState<number | 'all'>(getInitialPriorityFilter)
  const [sortOrder, setSortOrder] = useState<'newest' | 'oldest' | 'priority'>(getInitialSortOrder)

  const getInitialPriorityFilter = (): number | 'all' => {
    const param = searchParams.get('priority')
    if (param === 'all' || param === null) return 'all'
    const num = parseInt(param, 10)
    return [0, 1, 2, 3].includes(num) ? num : 'all'
  }
  const getInitialStatusFilter = (): string | 'all' => {
    const param = searchParams.get('status')
    if (param === 'all' || param === null) return 'all'
    const validStatuses = ['backlog', 'in_progress', 'qa', 'done', 'blocked']
    return validStatuses.includes(param) ? param : 'all'
  }
  const getInitialSortOrder = (): 'newest' | 'oldest' | 'priority' => {
    const param = searchParams.get('sort')
    if (param === 'newest' || param === 'oldest' || param === 'priority') return param
    return 'newest'
  }
  const getInitialViewMode = (): 'kanban' | 'list' => {
    const param = searchParams.get('view')
    if (param === 'list') return 'list'
    return 'kanban'
  }

  const [priorityFilter, setPriorityFilter] = useState<number | 'all'>(getInitialPriorityFilter)
  const [statusFilter, setStatusFilter] = useState<string | 'all'>(getInitialStatusFilter)
  const [sortOrder, setSortOrder] = useState<'newest' | 'oldest' | 'priority'>(getInitialSortOrder)