import React, { useState, useMemo, useEffect } from 'react';
import { Search, Filter, ArrowUpDown, ArrowUp, ArrowDown, List, Grid, ChevronDown, Columns, Plus, Settings, Terminal, FileText, HelpCircle, X } from 'lucide-react';
import { cn } from '../lib/utils';
import TaskCard from './TaskCard';
import CreateTaskModal from './CreateTaskModal';
import { useTaskMaster } from '../contexts/TaskMasterContext';
import Shell from './Shell';
import { api } from '../utils/api';
const TaskList = ({
tasks = [],
onTaskClick,
className = '',
showParentTasks = false,
defaultView = 'kanban', // 'list', 'grid', or 'kanban'
currentProject,
onTaskCreated,
onShowPRDEditor,
existingPRDs = [],
onRefreshPRDs
}) => {
const [searchTerm, setSearchTerm] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
const [priorityFilter, setPriorityFilter] = useState('all');
const [sortBy, setSortBy] = useState('id'); // 'id', 'title', 'status', 'priority', 'updated'
const [sortOrder, setSortOrder] = useState('asc'); // 'asc' or 'desc'
const [viewMode, setViewMode] = useState(defaultView);
const [showFilters, setShowFilters] = useState(false);
const [showCreateModal, setShowCreateModal] = useState(false);
const [showCLI, setShowCLI] = useState(false);
const [showHelpGuide, setShowHelpGuide] = useState(false);
const [isTaskMasterComplete, setIsTaskMasterComplete] = useState(false);
const [showPRDDropdown, setShowPRDDropdown] = useState(false);
const { projectTaskMaster, refreshProjects, refreshTasks, setCurrentProject } = useTaskMaster();
// Close PRD dropdown when clicking outside
useEffect(() => {
const handleClickOutside = (event) => {
if (showPRDDropdown && !event.target.closest('.relative')) {
setShowPRDDropdown(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [showPRDDropdown]);
// Get unique status values from tasks
const statuses = useMemo(() => {
const statusSet = new Set(tasks.map(task => task.status).filter(Boolean));
return Array.from(statusSet).sort();
}, [tasks]);
// Get unique priority values from tasks
const priorities = useMemo(() => {
const prioritySet = new Set(tasks.map(task => task.priority).filter(Boolean));
return Array.from(prioritySet).sort();
}, [tasks]);
// Filter and sort tasks
const filteredAndSortedTasks = useMemo(() => {
let filtered = tasks.filter(task => {
// Text search
const searchLower = searchTerm.toLowerCase();
const matchesSearch = !searchTerm ||
task.title.toLowerCase().includes(searchLower) ||
task.description?.toLowerCase().includes(searchLower) ||
task.id.toString().includes(searchLower);
// Status filter
const matchesStatus = statusFilter === 'all' || task.status === statusFilter;
// Priority filter
const matchesPriority = priorityFilter === 'all' || task.priority === priorityFilter;
return matchesSearch && matchesStatus && matchesPriority;
});
// Sort tasks
filtered.sort((a, b) => {
let aVal, bVal;
switch (sortBy) {
case 'title':
aVal = a.title.toLowerCase();
bVal = b.title.toLowerCase();
break;
case 'status':
// Custom status ordering: pending, in-progress, done, blocked, deferred, cancelled
const statusOrder = { pending: 1, 'in-progress': 2, done: 3, blocked: 4, deferred: 5, cancelled: 6 };
aVal = statusOrder[a.status] || 99;
bVal = statusOrder[b.status] || 99;
break;
case 'priority':
// Custom priority ordering: high should be sorted first in descending
const priorityOrder = { high: 3, medium: 2, low: 1 };
aVal = priorityOrder[a.priority] || 0;
bVal = priorityOrder[b.priority] || 0;
break;
case 'updated':
aVal = new Date(a.updatedAt || a.createdAt || 0);
bVal = new Date(b.updatedAt || b.createdAt || 0);
break;
case 'id':
default:
// Handle numeric and dotted IDs (1, 1.1, 1.2, 2, 2.1, etc.)
const parseId = (id) => {
const parts = id.toString().split('.');
return parts.map(part => parseInt(part, 10));
};
const aIds = parseId(a.id);
const bIds = parseId(b.id);
// Compare each part
for (let i = 0; i < Math.max(aIds.length, bIds.length); i++) {
const aId = aIds[i] || 0;
const bId = bIds[i] || 0;
if (aId !== bId) {
aVal = aId;
bVal = bId;
break;
}
}
break;
}
if (sortBy === 'updated') {
return sortOrder === 'asc' ? aVal - bVal : bVal - aVal;
}
if (typeof aVal === 'string') {
return sortOrder === 'asc' ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
}
return sortOrder === 'asc' ? aVal - bVal : bVal - aVal;
});
return filtered;
}, [tasks, searchTerm, statusFilter, priorityFilter, sortBy, sortOrder]);
// Organize tasks by status for Kanban view
const kanbanColumns = useMemo(() => {
const allColumns = [
{
id: 'pending',
title: '📋 To Do',
status: 'pending',
color: 'bg-slate-50 dark:bg-slate-900/50 border-slate-200 dark:border-slate-700',
headerColor: 'bg-slate-100 dark:bg-slate-800 text-slate-800 dark:text-slate-200'
},
{
id: 'in-progress',
title: '🚀 In Progress',
status: 'in-progress',
color: 'bg-blue-50 dark:bg-blue-900/50 border-blue-200 dark:border-blue-700',
headerColor: 'bg-blue-100 dark:bg-blue-800 text-blue-800 dark:text-blue-200'
},
{
id: 'done',
title: '✅ Done',
status: 'done',
color: 'bg-emerald-50 dark:bg-emerald-900/50 border-emerald-200 dark:border-emerald-700',
headerColor: 'bg-emerald-100 dark:bg-emerald-800 text-emerald-800 dark:text-emerald-200'
},
{
id: 'blocked',
title: '🚫 Blocked',
status: 'blocked',
color: 'bg-red-50 dark:bg-red-900/50 border-red-200 dark:border-red-700',
headerColor: 'bg-red-100 dark:bg-red-800 text-red-800 dark:text-red-200'
},
{
id: 'deferred',
title: '⏳ Deferred',
status: 'deferred',
color: 'bg-amber-50 dark:bg-amber-900/50 border-amber-200 dark:border-amber-700',
headerColor: 'bg-amber-100 dark:bg-amber-800 text-amber-800 dark:text-amber-200'
},
{
id: 'cancelled',
title: '❌ Cancelled',
status: 'cancelled',
color: 'bg-gray-50 dark:bg-gray-900/50 border-gray-200 dark:border-gray-700',
headerColor: 'bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200'
}
];
// Only show columns that have tasks or are part of the main workflow
const mainWorkflowStatuses = ['pending', 'in-progress', 'done'];
const columnsWithTasks = allColumns.filter(column => {
const hasTask = filteredAndSortedTasks.some(task => task.status === column.status);
const isMainWorkflow = mainWorkflowStatuses.includes(column.status);
return hasTask || isMainWorkflow;
});
return columnsWithTasks.map(column => ({
...column,
tasks: filteredAndSortedTasks.filter(task => task.status === column.status)
}));
}, [filteredAndSortedTasks]);
const handleSortChange = (newSortBy) => {
if (sortBy === newSortBy) {
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
} else {
setSortBy(newSortBy);
setSortOrder('asc');
}
};
const clearFilters = () => {
setSearchTerm('');
setStatusFilter('all');
setPriorityFilter('all');
};
const getSortIcon = (field) => {
if (sortBy !== field) return ;
return sortOrder === 'asc' ? : ;
};
if (tasks.length === 0) {
// Check if TaskMaster is configured by looking for .taskmaster directory
const hasTaskMasterDirectory = currentProject?.taskMasterConfigured ||
currentProject?.taskmaster?.hasTaskmaster ||
projectTaskMaster?.hasTaskmaster;
return (
{!hasTaskMasterDirectory ? (
// TaskMaster not configured
TaskMaster AI is not configured
TaskMaster helps break down complex projects into manageable tasks with AI-powered assistance
{/* What is TaskMaster section */}
🎯 What is TaskMaster?
• AI-Powered Task Management: Break complex projects into manageable subtasks
• PRD Templates: Generate tasks from Product Requirements Documents
• Dependency Tracking: Understand task relationships and execution order
• Progress Visualization: Kanban boards and detailed task analytics
• CLI Integration: Use taskmaster commands for advanced workflows
{
setIsTaskMasterComplete(false); // Reset completion state
setShowCLI(true);
}}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition-colors flex items-center gap-2 mx-auto"
>
Initialize TaskMaster AI
) : (
// TaskMaster configured but no tasks - show Getting Started guide
Getting Started with TaskMaster
TaskMaster is initialized! Here's what to do next:
{/* Step 1 */}
1
Create a Product Requirements Document (PRD)
Discuss your project idea and create a PRD that describes what you want to build.
{
onShowPRDEditor?.();
}}
className="inline-flex items-center gap-1 text-xs bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 px-2 py-1 rounded hover:bg-purple-200 dark:hover:bg-purple-900/50 transition-colors"
>
Add PRD
{/* Show existing PRDs if any */}
{existingPRDs.length > 0 && (
Existing PRDs:
{existingPRDs.map((prd) => (
{
try {
// Load the PRD content from the API
const response = await api.get(`/taskmaster/prd/${encodeURIComponent(currentProject.name)}/${encodeURIComponent(prd.name)}`);
if (response.ok) {
const prdData = await response.json();
onShowPRDEditor?.({
name: prd.name,
content: prdData.content,
isExisting: true
});
} else {
console.error('Failed to load PRD:', response.statusText);
}
} catch (error) {
console.error('Error loading PRD:', error);
}
}}
className="inline-flex items-center gap-1 text-xs bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 px-2 py-1 rounded hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
>
{prd.name}
))}
)}
{/* Step 2 */}
2
Generate Tasks from PRD
Once you have a PRD, ask your AI assistant to parse it and TaskMaster will automatically break it down into manageable tasks with implementation details.
{/* Step 3 */}
3
Analyze & Expand Tasks
Ask your AI assistant to analyze task complexity and expand them into detailed subtasks for easier implementation.
{/* Step 4 */}
4
Start Building
Ask your AI assistant to begin working on tasks, update their status, and add new tasks as your project evolves.
{
e.preventDefault();
e.stopPropagation();
onShowPRDEditor?.();
}}
className="flex items-center gap-2 px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white rounded-lg font-medium transition-colors cursor-pointer"
style={{ zIndex: 10 }}
>
Add PRD
💡 Tip: Start with a PRD to get the most out of TaskMaster's AI-powered task generation
)}
{/* TaskMaster CLI Setup Modal */}
{showCLI && (
{/* Modal Header */}
TaskMaster Setup
Interactive CLI for {currentProject?.displayName}
{
setShowCLI(false);
// Refresh project data after closing CLI to detect TaskMaster initialization
setTimeout(() => {
refreshProjects();
// Also refresh the current project's TaskMaster status
if (currentProject) {
setCurrentProject(currentProject);
}
}, 1000);
}}
className="p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800"
>
{/* Terminal Container */}
{
// Focus the terminal when clicked
const terminalElement = e.currentTarget.querySelector('.xterm-screen');
if (terminalElement) {
terminalElement.focus();
}
}}
>
{
setIsTaskMasterComplete(true);
if (exitCode === 0) {
// Auto-refresh after successful completion
setTimeout(() => {
refreshProjects();
if (currentProject) {
setCurrentProject(currentProject);
}
}, 1000);
}
}}
/>
{/* Modal Footer */}
{isTaskMasterComplete ? (
TaskMaster setup completed! You can now close this window.
) : (
"TaskMaster initialization will start automatically"
)}
{
setShowCLI(false);
setIsTaskMasterComplete(false); // Reset state
// Refresh project data after closing CLI to detect TaskMaster initialization
setTimeout(() => {
refreshProjects();
// Also refresh the current project's TaskMaster status
if (currentProject) {
setCurrentProject(currentProject);
}
}, 1000);
}}
className={cn(
"px-4 py-2 text-sm font-medium rounded-md transition-colors",
isTaskMasterComplete
? "bg-green-600 hover:bg-green-700 text-white"
: "text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600"
)}
>
{isTaskMasterComplete ? "Close & Continue" : "Close"}
)}
);
}
return (
{/* Header Controls */}
{/* Search Bar */}
setSearchTerm(e.target.value)}
className="pl-10 pr-4 py-2 w-full border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
{/* Controls */}
{/* View Toggle */}
setViewMode('kanban')}
className={cn(
'p-2 rounded-md transition-colors',
viewMode === 'kanban'
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
: 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'
)}
title="Kanban view"
>
setViewMode('list')}
className={cn(
'p-2 rounded-md transition-colors',
viewMode === 'list'
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
: 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'
)}
title="List view"
>
setViewMode('grid')}
className={cn(
'p-2 rounded-md transition-colors',
viewMode === 'grid'
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
: 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'
)}
title="Grid view"
>
{/* Filters Toggle */}
setShowFilters(!showFilters)}
className={cn(
'flex items-center gap-2 px-3 py-2 rounded-lg border transition-colors',
showFilters
? 'bg-blue-50 dark:bg-blue-900 border-blue-200 dark:border-blue-700 text-blue-700 dark:text-blue-300'
: 'bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700'
)}
>
Filters
{/* Action Buttons */}
{currentProject && (
<>
{/* Help Button */}
setShowHelpGuide(true)}
className="p-2 text-gray-600 dark:text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors border border-gray-300 dark:border-gray-600"
title="TaskMaster Getting Started Guide"
>
{/* PRD Management */}
{existingPRDs.length > 0 ? (
// Dropdown when PRDs exist
setShowPRDDropdown(!showPRDDropdown)}
className="flex items-center gap-2 px-3 py-2 bg-purple-600 hover:bg-purple-700 text-white rounded-lg transition-colors font-medium"
title={`${existingPRDs.length} PRD${existingPRDs.length > 1 ? 's' : ''} available`}
>
PRDs
{existingPRDs.length}
{showPRDDropdown && (
{
onShowPRDEditor?.();
setShowPRDDropdown(false);
}}
className="w-full text-left px-3 py-2 text-sm font-medium text-purple-700 dark:text-purple-300 hover:bg-purple-50 dark:hover:bg-purple-900/30 rounded flex items-center gap-2"
>
Create New PRD
Existing PRDs:
{existingPRDs.map((prd) => (
{
try {
const response = await api.get(`/taskmaster/prd/${encodeURIComponent(currentProject.name)}/${encodeURIComponent(prd.name)}`);
if (response.ok) {
const prdData = await response.json();
onShowPRDEditor?.({
name: prd.name,
content: prdData.content,
isExisting: true
});
setShowPRDDropdown(false);
}
} catch (error) {
console.error('Error loading PRD:', error);
}
}}
className="w-full text-left px-3 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded flex items-center gap-2"
title={`Modified: ${new Date(prd.modified).toLocaleDateString()}`}
>
{prd.name}
))}
)}
) : (
// Simple button when no PRDs exist
{
onShowPRDEditor?.();
}}
className="flex items-center gap-2 px-3 py-2 bg-purple-600 hover:bg-purple-700 text-white rounded-lg transition-colors font-medium"
title="Create Product Requirements Document"
>
Add PRD
)}
{/* Add Task Button */}
{((currentProject?.taskMasterConfigured || currentProject?.taskmaster?.hasTaskmaster || projectTaskMaster?.hasTaskmaster) || tasks.length > 0) && (
setShowCreateModal(true)}
className="flex items-center gap-2 px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors font-medium"
title="Add a new task"
>
Add Task
)}
>
)}
{/* Expanded Filters */}
{showFilters && (
{/* Status Filter */}
Status
setStatusFilter(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
>
All Statuses
{statuses.map(status => (
{status.charAt(0).toUpperCase() + status.slice(1).replace('-', ' ')}
))}
{/* Priority Filter */}
Priority
setPriorityFilter(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
>
All Priorities
{priorities.map(priority => (
{priority.charAt(0).toUpperCase() + priority.slice(1)}
))}
{/* Sort By */}
Sort By
{
const [field, order] = e.target.value.split('-');
setSortBy(field);
setSortOrder(order);
}}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
>
ID (Ascending)
ID (Descending)
Title (A-Z)
Title (Z-A)
Status (Pending First)
Status (Done First)
Priority (High First)
Priority (Low First)
{/* Filter Actions */}
Showing {filteredAndSortedTasks.length} of {tasks.length} tasks
Clear Filters
)}
{/* Quick Sort Buttons */}
handleSortChange('id')}
className={cn(
'flex items-center gap-1 px-3 py-1.5 rounded-md text-sm transition-colors',
sortBy === 'id'
? 'bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300'
: 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700'
)}
>
ID {getSortIcon('id')}
handleSortChange('status')}
className={cn(
'flex items-center gap-1 px-3 py-1.5 rounded-md text-sm transition-colors',
sortBy === 'status'
? 'bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300'
: 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700'
)}
>
Status {getSortIcon('status')}
handleSortChange('priority')}
className={cn(
'flex items-center gap-1 px-3 py-1.5 rounded-md text-sm transition-colors',
sortBy === 'priority'
? 'bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300'
: 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700'
)}
>
Priority {getSortIcon('priority')}
{/* Task Cards */}
{filteredAndSortedTasks.length === 0 ? (
No tasks match your filters
Try adjusting your search or filter criteria.
) : viewMode === 'kanban' ? (
/* Kanban Board Layout - Dynamic grid based on column count */
= 6 && "grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6"
)}>
{kanbanColumns.map((column) => (
{/* Column Header */}
{column.title}
{column.tasks.length}
{/* Column Tasks */}
{column.tasks.length === 0 ? (
No tasks yet
{column.status === 'pending' ? 'Tasks will appear here' :
column.status === 'in-progress' ? 'Move tasks here when started' :
column.status === 'done' ? 'Completed tasks appear here' :
'Tasks with this status will appear here'}
) : (
column.tasks.map((task) => (
onTaskClick?.(task)}
showParent={showParentTasks}
className="w-full shadow-sm hover:shadow-md transition-shadow cursor-pointer"
/>
))
)}
))}
) : (
{filteredAndSortedTasks.map((task) => (
onTaskClick?.(task)}
showParent={showParentTasks}
className={viewMode === 'grid' ? 'h-full' : ''}
/>
))}
)}
{/* Create Task Modal */}
{showCreateModal && (
setShowCreateModal(false)}
onTaskCreated={() => {
setShowCreateModal(false);
if (onTaskCreated) onTaskCreated();
}}
/>
)}
{/* Help Guide Modal */}
{showHelpGuide && (
{/* Modal Header */}
Getting Started with TaskMaster
Your guide to productive task management
setShowHelpGuide(false)}
className="p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
>
{/* Modal Content */}
{/* Step 1 */}
1
Create a Product Requirements Document (PRD)
Discuss your project idea and create a PRD that describes what you want to build.
{
onShowPRDEditor?.();
setShowHelpGuide(false);
}}
className="inline-flex items-center gap-2 text-sm bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 px-3 py-1.5 rounded-lg hover:bg-purple-200 dark:hover:bg-purple-900/50 transition-colors"
>
Add PRD
{/* Step 2 */}
2
Generate Tasks from PRD
Once you have a PRD, ask your AI assistant to parse it and TaskMaster will automatically break it down into manageable tasks with implementation details.
💬 Example:
"I've just initialized a new project with Claude Task Master. I have a PRD at .taskmaster/docs/prd.txt. Can you help me parse it and set up the initial tasks?"
{/* Step 3 */}
3
Analyze & Expand Tasks
Ask your AI assistant to analyze task complexity and expand them into detailed subtasks for easier implementation.
💬 Example:
"Task 5 seems complex. Can you break it down into subtasks?"
{/* Step 4 */}
4
Start Building
Ask your AI assistant to begin working on tasks, update their status, and add new tasks as your project evolves.
💬 Example:
"Please add a new task to implement user profile image uploads using Cloudinary, research the best approach."
View more examples and usage patterns →
{/* Pro Tips */}
💡 Pro Tips
Use the search bar to quickly find specific tasks
Switch between Kanban, List, and Grid views using the view toggles
Use filters to focus on specific task statuses or priorities
Click on any task to view detailed information and manage subtasks
{/* Learn More Section */}
📚 Learn More
TaskMaster AI is an advanced task management system built for developers. Get documentation, examples, and contribute to the project.
View on GitHub
)}
);
};
export default TaskList;