/** * Main Kanban Board Component */ import React, { useState, useCallback, useEffect } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import { RefreshCw } from 'lucide-react'; import { type DragEndEvent, KanbanBoard, KanbanCards, KanbanHeader, KanbanProvider } from '@/components/ui/shadcn-io/kanban'; import { TaskCard } from './TaskCard'; import { TaskEditModal } from './TaskEditModal'; import { PollingStatus } from './PollingStatus'; import { TagDropdown } from './TagDropdown'; import { EmptyState } from './EmptyState'; import { useVSCodeContext } from '../contexts/VSCodeContext'; import { useTasks, useUpdateTaskStatus, useUpdateTask, taskKeys } from '../hooks/useTaskQueries'; import { kanbanStatuses, HEADER_HEIGHT } from '../constants'; import type { TaskMasterTask, TaskUpdates } from '../types'; export const TaskMasterKanban: React.FC = () => { const { state, dispatch, sendMessage, availableHeight } = useVSCodeContext(); const queryClient = useQueryClient(); const { error: legacyError, editingTask, polling, currentTag, availableTags } = state; const [activeTask, setActiveTask] = useState(null); const [isRefreshing, setIsRefreshing] = useState(false); // Use React Query to fetch tasks const { data: serverTasks = [], isLoading, error, isFetching, isSuccess } = useTasks({ tag: currentTag }); const updateTaskStatus = useUpdateTaskStatus(); const updateTask = useUpdateTask(); // Debug logging console.log('🔍 TaskMasterKanban Query State:', { isLoading, isFetching, isSuccess, tasksCount: serverTasks?.length, error }); // Temporary state only for active drag operations const [tempReorderedTasks, setTempReorderedTasks] = useState< TaskMasterTask[] | null >(null); // Use temp tasks only if actively set, otherwise use server tasks const tasks = tempReorderedTasks ?? serverTasks; // Calculate header height for proper kanban board sizing const kanbanHeight = availableHeight - HEADER_HEIGHT; // Group tasks by status const tasksByStatus = kanbanStatuses.reduce( (acc, status) => { acc[status.id] = tasks.filter((task) => task.status === status.id); return acc; }, {} as Record ); // Debug logging console.log('TaskMasterKanban render:', { tasksCount: tasks.length, currentTag, tasksByStatus: Object.entries(tasksByStatus).map(([status, tasks]) => ({ status, count: tasks.length, taskIds: tasks.map((t) => t.id) })), allTaskIds: tasks.map((t) => ({ id: t.id, title: t.title })) }); // Handle task update const handleUpdateTask = async (taskId: string, updates: TaskUpdates) => { console.log(`🔄 Updating task ${taskId} content:`, updates); try { await updateTask.mutateAsync({ taskId, updates, options: { append: false, research: false } }); console.log(`✅ Task ${taskId} content updated successfully`); // Close the edit modal dispatch({ type: 'SET_EDITING_TASK', payload: { taskId: null } }); } catch (error) { console.error(`❌ Failed to update task ${taskId}:`, error); dispatch({ type: 'SET_ERROR', payload: `Failed to update task: ${error}` }); } }; // Handle drag start const handleDragStart = useCallback( (event: DragEndEvent) => { const taskId = event.active.id as string; const task = tasks.find((t) => t.id === taskId); if (task) { setActiveTask(task); } }, [tasks] ); // Handle drag cancel const handleDragCancel = useCallback(() => { setActiveTask(null); // Clear any temporary state setTempReorderedTasks(null); }, []); // Handle drag end const handleDragEnd = useCallback( async (event: DragEndEvent) => { const { active, over } = event; // Reset active task setActiveTask(null); if (!over || active.id === over.id) { // Clear any temp state if drag was cancelled setTempReorderedTasks(null); return; } const taskId = active.id as string; const newStatus = over.id as TaskMasterTask['status']; // Find the task const task = tasks.find((t) => t.id === taskId); if (!task || task.status === newStatus) { // Clear temp state if no change needed setTempReorderedTasks(null); return; } // Create the optimistically reordered tasks const reorderedTasks = tasks.map((t) => t.id === taskId ? { ...t, status: newStatus } : t ); // Set temporary state to show immediate visual feedback setTempReorderedTasks(reorderedTasks); try { // Update on server - React Query will handle optimistic updates await updateTaskStatus.mutateAsync({ taskId, newStatus }); // Clear temp state after mutation starts successfully setTempReorderedTasks(null); } catch (error) { // On error, clear temp state - React Query will revert optimistic update setTempReorderedTasks(null); dispatch({ type: 'SET_ERROR', payload: `Failed to update task status: ${error}` }); } }, [tasks, updateTaskStatus, dispatch] ); // Handle retry connection const handleRetry = useCallback(() => { sendMessage({ type: 'retryConnection' }); }, [sendMessage]); // Handle refresh const handleRefresh = useCallback(async () => { setIsRefreshing(true); try { // Invalidate all task queries await queryClient.invalidateQueries({ queryKey: taskKeys.all }); } finally { // Reset after a short delay to show the animation setTimeout(() => setIsRefreshing(false), 500); } }, [queryClient]); // Handle tag switching const handleTagSwitch = useCallback( async (tagName: string) => { console.log('Switching to tag:', tagName); await sendMessage({ type: 'switchTag', data: { tagName } }); dispatch({ type: 'SET_TAG_DATA', payload: { currentTag: tagName, availableTags } }); }, [sendMessage, dispatch, availableTags] ); // Use React Query loading state const displayError = error ? error instanceof Error ? error.message : String(error) : legacyError; if (isLoading) { return (

Loading tasks...

); } if (displayError) { return (

Error: {displayError}

); } return ( <>

TaskMaster Kanban

{state.connectionStatus}
{tasks.length === 0 ? ( ) : ( : null } >
{kanbanStatuses.map((status) => { const statusTasks = tasksByStatus[status.id] || []; const hasScrollbar = statusTasks.length > 4; return (
{statusTasks.map((task) => ( { console.log( '🔍 Navigating to task details:', taskId ); dispatch({ type: 'NAVIGATE_TO_TASK', payload: taskId }); }} /> ))}
); })}
)}
{/* Task Edit Modal */} {editingTask?.taskId && editingTask.editData && ( { dispatch({ type: 'SET_EDITING_TASK', payload: { taskId: null } }); }} /> )} ); };