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';
import { useTranslation } from 'react-i18next';
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();
const { t } = useTranslation('tasks');
// 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: t('kanban.pending'),
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: t('kanban.inProgress'),
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: t('kanban.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: t('kanban.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: t('kanban.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: t('kanban.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, t]);
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
{t('notConfigured.title')}
{t('notConfigured.description')}
{/* What is TaskMaster section */}
{t('notConfigured.whatIsTitle')}
• {t('notConfigured.features.aiPowered')}
• {t('notConfigured.features.prdTemplates')}
• {t('notConfigured.features.dependencyTracking')}
• {t('notConfigured.features.progressVisualization')}
• {t('notConfigured.features.cliIntegration')}
) : (
// TaskMaster configured but no tasks - show Getting Started guide
{t('gettingStarted.title')}
{t('gettingStarted.subtitle')}
{/* Step 1 */}
1
{t('gettingStarted.steps.createPRD.title')}
{t('gettingStarted.steps.createPRD.description')}
{/* Show existing PRDs if any */}
{existingPRDs.length > 0 && (
{t('gettingStarted.steps.createPRD.existingPRDs')}
{existingPRDs.map((prd) => (
))}
)}
{/* Step 2 */}
2
{t('gettingStarted.steps.generateTasks.title')}
{t('gettingStarted.steps.generateTasks.description')}
{/* Step 3 */}
3
{t('gettingStarted.steps.analyzeTasks.title')}
{t('gettingStarted.steps.analyzeTasks.description')}
{/* Step 4 */}
4
{t('gettingStarted.steps.startBuilding.title')}
{t('gettingStarted.steps.startBuilding.description')}
{t('gettingStarted.tip')}
)}
{/* TaskMaster CLI Setup Modal */}
{showCLI && (
{/* Modal Header */}
{t('setupModal.title')}
{t('setupModal.subtitle', { projectName: currentProject?.displayName })}
{/* 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 ? (
{t('setupModal.completed')}
) : (
t('setupModal.willStart')
)}
)}
);
}
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 */}
{/* Filters Toggle */}
{/* Action Buttons */}
{currentProject && (
<>
{/* Help Button */}
{/* PRD Management */}
{existingPRDs.length > 0 ? (
// Dropdown when PRDs exist
{showPRDDropdown && (
{t('gettingStarted.steps.createPRD.existingPRDs')}
{existingPRDs.map((prd) => (
))}
)}
) : (
// Simple button when no PRDs exist
)}
{/* Add Task Button */}
{((currentProject?.taskMasterConfigured || currentProject?.taskmaster?.hasTaskmaster || projectTaskMaster?.hasTaskmaster) || tasks.length > 0) && (
)}
>
)}
{/* Expanded Filters */}
{showFilters && (
{/* Status Filter */}
{/* Priority Filter */}
{/* Sort By */}
{/* Filter Actions */}
{t('filters.showing', { filtered: filteredAndSortedTasks.length, total: tasks.length })}
)}
{/* Quick Sort Buttons */}
{/* Task Cards */}
{filteredAndSortedTasks.length === 0 ? (
{t('noMatchingTasks.title')}
{t('noMatchingTasks.description')}
) : 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 ? (
{t('kanban.noTasksYet')}
{column.status === 'pending' ? t('kanban.tasksWillAppear') :
column.status === 'in-progress' ? t('kanban.moveTasksHere') :
column.status === 'done' ? t('kanban.completedTasksHere') :
t('kanban.statusTasksHere')}
) : (
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 */}
{t('helpGuide.title')}
{t('helpGuide.subtitle')}
{/* Modal Content */}
{/* Step 1 */}
1
{t('gettingStarted.steps.createPRD.title')}
{t('gettingStarted.steps.createPRD.description')}
{/* Step 2 */}
2
{t('gettingStarted.steps.generateTasks.title')}
{t('gettingStarted.steps.generateTasks.description')}
{t('helpGuide.examples.parsePRD')}
{/* Step 3 */}
3
{t('gettingStarted.steps.analyzeTasks.title')}
{t('gettingStarted.steps.analyzeTasks.description')}
{t('helpGuide.examples.expandTask')}
{/* Step 4 */}
4
{t('gettingStarted.steps.startBuilding.title')}
{t('gettingStarted.steps.startBuilding.description')}
{t('helpGuide.examples.addTask')}
{t('helpGuide.moreExamples')}
{/* Pro Tips */}
{t('helpGuide.proTips.title')}
-
{t('helpGuide.proTips.search')}
-
{t('helpGuide.proTips.views')}
-
{t('helpGuide.proTips.filters')}
-
{t('helpGuide.proTips.details')}
{/* Learn More Section */}
)}
);
};
export default TaskList;